diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..37a4eb8b43d978ea05b2c6e3fb934f9b40dddd4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +/entry/.preview +.cxx diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 95087f096ddd368a3dde440806e88cd7b0499c1f..363a1689c8d0a663fea441df8a52c748ada483a2 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -1,3 +1,8 @@ +## 0.0.1-SNAPSHOT +> 2021/5/12 + +* refactor: For module library Android-to-OpenHarmony + ### 0.1.5 > 2017.06.28 diff --git a/PROPERTY.md b/PROPERTY.md index 534b959fc759a89918383eba44f3c7849d5adc21..a2d024905c4ee61dfba936b78d80a4e0e4caed33 100644 --- a/PROPERTY.md +++ b/PROPERTY.md @@ -2,16 +2,16 @@ | XML | Java Code | Comment | Since | | -------------------- | ------------------- | --------------------------- | ----- | -| app:xLabelSize | setXLabelSize | X 轴标签字符大小 | 0.1.0 | -| app:xLabelColor | setXLabelColor | X 轴标签字符颜色 | 0.1.0 | -| app:xLabelViewHeight | setXLabelViewHeight | X 轴 Label 区域的高度 | 0.1.0 | -| app:yLabelSize | setYLabelSize | Y 轴标签字符大小 | 0.1.0 | -| app:yLabelColor | setYLabelColor | Y 轴标签字符颜色 | 0.1.0 | -| app:yLabelAlign | setYLabelAlign | Y 轴标签对齐方向 1: left, 2: right | 0.1.2 | -| app:axisSize | setAxisSize | 轴线条大小 | 0.1.0 | -| app:axisColor | setAxisColor | 轴线条颜色 | 0.1.0 | -| app:gridSize | setGridSize | 网格线大小 | 0.1.0 | -| app:gridColor | setGridColor | 网格线颜色 | 0.1.0 | +| ohos:xLabelSize | setXLabelSize | X 轴标签字符大小 | 0.1.0 | +| ohos:xLabelColor | setXLabelColor | X 轴标签字符颜色 | 0.1.0 | +| ohos:xLabelViewHeight | setXLabelViewHeight | X 轴 Label 区域的高度 | 0.1.0 | +| ohos:yLabelSize | setYLabelSize | Y 轴标签字符大小 | 0.1.0 | +| ohos:yLabelColor | setYLabelColor | Y 轴标签字符颜色 | 0.1.0 | +| ohos:yLabelAlign | setYLabelAlign | Y 轴标签对齐方向 1: left, 2: right | 0.1.2 | +| ohos:axisSize | setAxisSize | 轴线条大小 | 0.1.0 | +| ohos:axisColor | setAxisColor | 轴线条颜色 | 0.1.0 | +| ohos:gridSize | setGridSize | 网格线大小 | 0.1.0 | +| ohos:gridColor | setGridColor | 网格线颜色 | 0.1.0 | @@ -19,14 +19,14 @@ | XML | Java Code | Comment | Since | | --------------------- | -------------------- | ------------------- | ----- | -| app:highlightSize | setHighlightSize | 高亮线条大小 | 0.1.0 | -| app:highlightColor | setHighlightColor | 高亮线条颜色 | 0.1.0 | -| app:markerBorderSize | setMarkerBorderSize | MarkerView 边框大小 | 0.1.0 | -| app:markerBorderColor | setMarkerBorderColor | MarkerView 边框颜色 | 0.1.0 | -| app:markerTextSize | setMarkerTextSize | MarkerView 字符大小 | 0.1.0 | -| app:markerTextColor | setMarkerTextColor | MarkerView 字符颜色 | 0.1.0 | -| app:xMarkerAlign | setXMarkerAlign | X 轴 MarkerView 对齐方向 | 0.1.3 | -| app:yMarkerAlign | setYMarkerAlign | Y 轴 MarkerView 对齐方向 | 0.1.3 | +| ohos:highlightSize | setHighlightSize | 高亮线条大小 | 0.1.0 | +| ohos:highlightColor | setHighlightColor | 高亮线条颜色 | 0.1.0 | +| ohos:markerBorderSize | setMarkerBorderSize | MarkerView 边框大小 | 0.1.0 | +| ohos:markerBorderColor | setMarkerBorderColor | MarkerView 边框颜色 | 0.1.0 | +| ohos:markerTextSize | setMarkerTextSize | MarkerView 字符大小 | 0.1.0 | +| ohos:markerTextColor | setMarkerTextColor | MarkerView 字符颜色 | 0.1.0 | +| ohos:xMarkerAlign | setXMarkerAlign | X 轴 MarkerView 对齐方向 | 0.1.3 | +| ohos:yMarkerAlign | setYMarkerAlign | Y 轴 MarkerView 对齐方向 | 0.1.3 | @@ -34,9 +34,9 @@ | XML | Java Code | Comment | Since | | -------------------- | ------------------- | ---------------------------------------- | ----- | -| app:timeLineSize | setTimeLineSize | 分时线大小 | 0.1.0 | -| app:timeLineColor | setTimeLineColor | 分时线颜色 | 0.1.0 | -| app:timeLineMaxCount | setTimeLineMaxCount | 分时图 entry 最多个数。注:此值与 entrySet 里的 entries.size() 意义不同,这里指 X 轴上最多能容纳多少个 entry | 0.1.4 | +| ohos:timeLineSize | setTimeLineSize | 分时线大小 | 0.1.0 | +| ohos:timeLineColor | setTimeLineColor | 分时线颜色 | 0.1.0 | +| ohos:timeLineMaxCount | setTimeLineMaxCount | 分时图 entry 最多个数。注:此值与 entrySet 里的 entries.size() 意义不同,这里指 X 轴上最多能容纳多少个 entry | 0.1.4 | @@ -44,18 +44,18 @@ | XML | Java Code | Comment | Since | | ------------------------------- | ------------------------------ | -------------- | ----- | -| app:candleBorderSize | setCandleBorderSize | 蜡烛图矩形边框大小 | 0.1.0 | -| app:candleExtremumLabelSize | setCandleExtremumLabelSize | 蜡烛图极值字符大小 | 0.1.0 | -| app:candleExtremumLableColor | setCandleExtremumLableColor | 蜡烛图极值字符颜色 | 0.1.0 | -| app:shadowSize | setShadowSize | 影线大小 | 0.1.0 | -| app:increasingColor | setIncreasingColor | 上涨颜色 | 0.1.0 | -| app:decreasingColor | setDecreasingColor | 下跌颜色 | 0.1.0 | -| app:neutralColor | setNeutralColor | 不涨不跌颜色 | 0.1.0 | -| app:portraitDefaultVisibleCount | setPortraitDefaultVisibleCount | 竖屏默认显示多少个蜡烛图 | 0.1.0 | -| app:zoomInTimes | setZoomInTimes | 最多放大次数 | 0.1.0 | -| app:zoomOutTimes | setZoomOutTimes | 最多缩小次数 | 0.1.0 | -| app:increasingStyle | setIncreasingStyle | 上涨蜡烛图填充样式。默认实心 | 0.1.4 | -| app:decreasingStyle | setDecreasingStyle | 下跌蜡烛图填充样式,默认实心 | 0.1.4 | +| ohos:candleBorderSize | setCandleBorderSize | 蜡烛图矩形边框大小 | 0.1.0 | +| ohos:candleExtremumLabelSize | setCandleExtremumLabelSize | 蜡烛图极值字符大小 | 0.1.0 | +| ohos:candleExtremumLableColor | setCandleExtremumLableColor | 蜡烛图极值字符颜色 | 0.1.0 | +| ohos:shadowSize | setShadowSize | 影线大小 | 0.1.0 | +| ohos:increasingColor | setIncreasingColor | 上涨颜色 | 0.1.0 | +| ohos:decreasingColor | setDecreasingColor | 下跌颜色 | 0.1.0 | +| ohos:neutralColor | setNeutralColor | 不涨不跌颜色 | 0.1.0 | +| ohos:portraitDefaultVisibleCount | setPortraitDefaultVisibleCount | 竖屏默认显示多少个蜡烛图 | 0.1.0 | +| ohos:zoomInTimes | setZoomInTimes | 最多放大次数 | 0.1.0 | +| ohos:zoomOutTimes | setZoomOutTimes | 最多缩小次数 | 0.1.0 | +| ohos:increasingStyle | setIncreasingStyle | 上涨蜡烛图填充样式。默认实心 | 0.1.4 | +| ohos:decreasingStyle | setDecreasingStyle | 下跌蜡烛图填充样式,默认实心 | 0.1.4 | @@ -63,36 +63,36 @@ | XML | Java Code | Comment | Since | | -------------------------- | ------------------------- | --------------- | ----- | -| app:maLineSize | setMaLineSize | MA 平均线大小 | 0.1.0 | -| app:ma5Color | setMa5Color | MA5 平均线颜色 | 0.1.0 | -| app:ma10Color | setMa10Color | MA10 平均线颜色 | 0.1.0 | -| app:ma20Color | setMa20Color | MA20 平均线颜色 | 0.1.0 | -| app:bollLineSize | setBollLineSize | BOLL 线条大小 | 0.1.0 | -| app:bollMidLineColor | setBollMidLineColor | BOLL MID 线条颜色 | 0.1.0 | -| app:bollUpperLineColor | setBollUpperLineColor | BOLL UPPER 线条颜色 | 0.1.0 | -| app:bollLowerLineColor | setBollLowerLineColor | BOLL LOWER 线条颜色 | 0.1.0 | -| app:kdjLineSize | setKdjLineSize | KDJ 线条大小 | 0.1.0 | -| app:kdjKLineColor | setKdjKLineColor | KDJ K 线条颜色 | 0.1.0 | -| app:kdjDLineColor | setKdjDLineColor | KDJ D 线条颜色 | 0.1.0 | -| app:kdjJLineColor | setKdjJLineColor | KDJ J 线条颜色 | 0.1.0 | -| app:macdLineSize | setMacdLineSize | MACD 两条线大小 | 0.1.0 | -| app:macdHighlightTextColor | setMacdHighlightTextColor | 高亮的 MACD 字符颜色 | 0.1.0 | -| app:deaLineColor | setDeaLineColor | DEA 线条颜色 | 0.1.0 | -| app:diffLineColor | setDiffLineColor | DIFF 线条颜色 | 0.1.0 | -| app:rsiLineSize | setRsiLineSize | RSI 线条大小 | 0.1.0 | -| app:rsi1LineColor | setRsi1LineColor | RSI 第一条线颜色 | 0.1.0 | -| app:rsi2LineColor | setRsi2LineColor | RSI 第二条线颜色 | 0.1.0 | -| app:rsi3LineColor | setRsi3LineColor | RSI 第三条线颜色 | 0.1.0 | -| app:maTextSize | setMaTextSize | MA 字符大小 | 0.1.0 | -| app:maTextColor | setMaTextColor | MA 字符颜色 | 0.1.0 | -| app:bollTextSize | setBollTextSize | BOLL 字符大小 | 0.1.0 | -| app:bollTextColor | setBollTextColor | BOLL 字符颜色 | 0.1.0 | -| app:kdjTextSize | setKdjTextSize | KDJ 字符大小 | 0.1.0 | -| app:kdjTextColor | setKdjTextColor | KDJ 字符颜色 | 0.1.0 | -| app:macdTextSize | setMacdTextSize | MACD 字符大小 | 0.1.0 | -| app:macdTextColor | setMacdTextColor | MACD 字符颜色 | 0.1.0 | -| app:rsiTextSize | setRsiTextSize | RSI 字符大小 | 0.1.0 | -| app:rsiTextColor | setRsiTextColor | RSI 字符颜色 | 0.1.0 | +| ohos:maLineSize | setMaLineSize | MA 平均线大小 | 0.1.0 | +| ohos:ma5Color | setMa5Color | MA5 平均线颜色 | 0.1.0 | +| ohos:ma10Color | setMa10Color | MA10 平均线颜色 | 0.1.0 | +| ohos:ma20Color | setMa20Color | MA20 平均线颜色 | 0.1.0 | +| ohos:bollLineSize | setBollLineSize | BOLL 线条大小 | 0.1.0 | +| ohos:bollMidLineColor | setBollMidLineColor | BOLL MID 线条颜色 | 0.1.0 | +| ohos:bollUpperLineColor | setBollUpperLineColor | BOLL UPPER 线条颜色 | 0.1.0 | +| ohos:bollLowerLineColor | setBollLowerLineColor | BOLL LOWER 线条颜色 | 0.1.0 | +| ohos:kdjLineSize | setKdjLineSize | KDJ 线条大小 | 0.1.0 | +| ohos:kdjKLineColor | setKdjKLineColor | KDJ K 线条颜色 | 0.1.0 | +| ohos:kdjDLineColor | setKdjDLineColor | KDJ D 线条颜色 | 0.1.0 | +| ohos:kdjJLineColor | setKdjJLineColor | KDJ J 线条颜色 | 0.1.0 | +| ohos:macdLineSize | setMacdLineSize | MACD 两条线大小 | 0.1.0 | +| ohos:macdHighlightTextColor | setMacdHighlightTextColor | 高亮的 MACD 字符颜色 | 0.1.0 | +| ohos:deaLineColor | setDeaLineColor | DEA 线条颜色 | 0.1.0 | +| ohos:diffLineColor | setDiffLineColor | DIFF 线条颜色 | 0.1.0 | +| ohos:rsiLineSize | setRsiLineSize | RSI 线条大小 | 0.1.0 | +| ohos:rsi1LineColor | setRsi1LineColor | RSI 第一条线颜色 | 0.1.0 | +| ohos:rsi2LineColor | setRsi2LineColor | RSI 第二条线颜色 | 0.1.0 | +| ohos:rsi3LineColor | setRsi3LineColor | RSI 第三条线颜色 | 0.1.0 | +| ohos:maTextSize | setMaTextSize | MA 字符大小 | 0.1.0 | +| ohos:maTextColor | setMaTextColor | MA 字符颜色 | 0.1.0 | +| ohos:bollTextSize | setBollTextSize | BOLL 字符大小 | 0.1.0 | +| ohos:bollTextColor | setBollTextColor | BOLL 字符颜色 | 0.1.0 | +| ohos:kdjTextSize | setKdjTextSize | KDJ 字符大小 | 0.1.0 | +| ohos:kdjTextColor | setKdjTextColor | KDJ 字符颜色 | 0.1.0 | +| ohos:macdTextSize | setMacdTextSize | MACD 字符大小 | 0.1.0 | +| ohos:macdTextColor | setMacdTextColor | MACD 字符颜色 | 0.1.0 | +| ohos:rsiTextSize | setRsiTextSize | RSI 字符大小 | 0.1.0 | +| ohos:rsiTextColor | setRsiTextColor | RSI 字符颜色 | 0.1.0 | @@ -100,9 +100,9 @@ | XML | Java Code | Comment | Since | | -------------------- | ------------------- | ------------ | ----- | -| app:loadingTextSize | setLoadingTextSize | loading 字符大小 | 0.1.0 | -| app:loadingTextColor | setLoadingTextColor | loading 字符颜色 | 0.1.0 | -| app:loadingText | setLoadingText | loading 字符 | 0.1.0 | -| app:errorTextSize | setErrorTextSize | error 字符大小 | 0.1.0 | -| app:errorTextColor | setErrorTextColor | error 字符颜色 | 0.1.0 | -| app:errorText | setErrorText | error 字符 | 0.1.0 | \ No newline at end of file +| ohos:loadingTextSize | setLoadingTextSize | loading 字符大小 | 0.1.0 | +| ohos:loadingTextColor | setLoadingTextColor | loading 字符颜色 | 0.1.0 | +| ohos:loadingText | setLoadingText | loading 字符 | 0.1.0 | +| ohos:errorTextSize | setErrorTextSize | error 字符大小 | 0.1.0 | +| ohos:errorTextColor | setErrorTextColor | error 字符颜色 | 0.1.0 | +| ohos:errorText | setErrorText | error 字符 | 0.1.0 | \ No newline at end of file diff --git a/README.md b/README.md index f8dfa9800a00303bfcdcf97aaa542c39c0594690..be311be170b6ea7813682324ad04959be77ac1c0 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,84 @@ -## Features - -当前最新版本:0.1.5 - -支持在 XML 布局文件和代码中设置各个线条颜色、大小配置 - -支持左滑、右滑加载 - -支持长按高亮、短按点击、双指缩放事件 - -支持 fling 滑动 - -支持 MACD、RSI、KDJ、BOLL 四个指标 +# ikvStockChart + +**本项目基于开源项目ikvStockChart 进行openharmony的移植和开发,可以通过项目标签以及github地址( https://github.com/wordplat/ikvStockChart )追踪到原项目版本** + +#### 项目介绍 +- 项目名称:ikvStockChart +- 所属系列:openharmony的第三方组件适配移植 +- 功能:ikvStockChart一个简单的openharmony图表库,支持时间线,k线,macd,kdj,rsi,boll索引和交互式手势操作,包括左右滑动刷新,缩放,突出显示。 +- 项目移植状态:主功能完成 +- 调用差异:无 +- 开发版本:sdk5,DevEco Studio2.1 beta4 +- 项目作者和维护人:蒋军 +- 联系方式:jiangjun073@chinasoftinc.com +- 原项目Doc地址:https://github.com/wordplat/ikvStockChart +- 基线版本:ikvStockChart组件Release版本号 0.1.3.1 + +#### 支持的功能 +- 支持在 XML 布局文件和代码中设置各个线条颜色、大小配置 +- 支持左滑、右滑加载 +- 支持长按高亮、短按点击、双指缩放事件 +- 支持 fling 滑动 +- 支持 MACD、RSI、KDJ、BOLL 四个指标 +- 支持自定义的指标显示方式 +- 附带的程序示例有:默认左滑右滑加载、禁用左滑右滑加载、多个指标共同联动显示、在 Fragment 中使用、带有下拉刷新的需求中使用、横竖屏切换(自动旋转)、简单分时图 + +#### 效果演示 +![](https://images.gitee.com/uploads/images/2021/0515/155903_ed3f65ad_8751121.png "screenshots0.PNG") +![](https://images.gitee.com/uploads/images/2021/0515/155924_8ae9f455_8751121.png "screenshots1.PNG") +![](https://images.gitee.com/uploads/images/2021/0515/155935_33088690_8751121.png "screenshots2.PNG") + +#### 安装教程 + +1.在项目根目录下的build.gradle文件中, + + ```gradle +allprojects { + repositories { + maven { + url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + } + } +} + ``` -支持自定义的指标显示方式 +2.在entry模块的build.gradle文件中, -> 附带的程序示例有:默认左滑右滑加载、禁用左滑右滑加载、多个指标共同联动显示、在 Fragment 中使用、带有下拉刷新的需求中使用、横竖屏切换(自动旋转)、简单分时图 + ```gradle + dependencies { + implementation('com.gitee.chinasoft_ohos:timetable-view:0.0.1-SNAPSHOT') + ...... + } + ``` -## Change log +在sdk5,DevEco Studio2.1 beta4下项目可直接运行 -> 2017.06.28 +如无法运行,删除项目.gradle,.idea,build,gradle,build.gradle文件, -增加 K线图成交量柱状图 +并依据自己的版本创建新项目,将新项目的对应文件复制到根目录下 -![S70413-225235](Screenshots/S70628-230542.jpg) +#### 使用说明 -查看全部日志:[https://github.com/wordplat/ikvStockChart/blob/master/CHANGE_LOG.md](https://github.com/wordplat/ikvStockChart/blob/master/CHANGE_LOG.md) +##### 添加控件 +该控件包含的基础组件有日期栏、侧边栏、课表视图,在布局文件中加入如下代码后会包含这三个基础组件,注意要添加背景色,没有背景图片可以添加白色背景。 -## Usage +XML中添加控件: -```groovy -repositories { - jcenter() -} +```xml -dependencies { - compile 'com.wordplat:ikvStockChart:0.1.5' -} + ``` -```xml - -``` +##### 代码使用 + ```java - final EntrySet entrySet = new EntrySet(); + + final EntrySet entrySet = new EntrySet(); entrySet.addEntry(new Entry(...)); kLineLayout.getKLineView().setEntrySet(entrySet); @@ -86,43 +118,45 @@ dependencies { ikvStockChart 支持 66 个属性配置,设置各个线条颜色大小参考 [https://github.com/wordplat/ikvStockChart/blob/master/PROPERTY.md](https://github.com/wordplat/ikvStockChart/blob/master/PROPERTY.md) + ```java - SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); - sizeColor.setXXX(); + + SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); + sizeColor.setXXX(); + ``` +#### 测试信息 -## Screenshot +CodeCheck代码测试无异常 -![S70413-224859](Screenshots/S70413-224859.jpg) ![S70413-224945](Screenshots/S70413-224945.jpg) ![S70413-225013](Screenshots/S70413-225013.jpg) +CloudTest代码测试无异常 -![S70413-225055](Screenshots/S70413-225055.jpg) ![S70413-225125](Screenshots/S70413-225125.jpg) ![S70413-225235](Screenshots/S70413-225235.jpg) +火绒安全病毒安全检测通过 -![S70417-010650](Screenshots/S70417-010650.jpg) +当前版本demo功能与原组件基本无差异 -## Dependency +测试员:陈翔,朱品,邓世雄 -none +#### 版本迭代 -## License +- 0.0.1-SNAPSHOT -```java -/* - * Copyright (C) 2017 WordPlat Open Source Project - * - * https://wordplat.com/InteractiveKLineView/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -``` \ No newline at end of file +#### 版权和许可信息 + + Copyright (C) 2017 WordPlat Open Source Project + + https://wordplat.com/InteractiveKLineView/ + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Screenshots/.keep b/Screenshots/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Screenshots/S70413-224859.jpg b/Screenshots/S70413-224859.jpg deleted file mode 100644 index 137fe86d83d2de6fdbb06fe1de5e63e6ae06084f..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-224859.jpg and /dev/null differ diff --git a/Screenshots/S70413-224945.jpg b/Screenshots/S70413-224945.jpg deleted file mode 100644 index 9d7b6f846c0ec30a78c9101a19e8023715748cca..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-224945.jpg and /dev/null differ diff --git a/Screenshots/S70413-225013.jpg b/Screenshots/S70413-225013.jpg deleted file mode 100644 index 3db2eef7415cd15fd4c090223880f765a7ac3429..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-225013.jpg and /dev/null differ diff --git a/Screenshots/S70413-225055.jpg b/Screenshots/S70413-225055.jpg deleted file mode 100644 index f2e4dd81cfcf3b06f30cd6aaf36edd37405dd1ea..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-225055.jpg and /dev/null differ diff --git a/Screenshots/S70413-225125.jpg b/Screenshots/S70413-225125.jpg deleted file mode 100644 index ff53f4afe44970f84f3ca4bbb808762dbfedf199..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-225125.jpg and /dev/null differ diff --git a/Screenshots/S70413-225235.jpg b/Screenshots/S70413-225235.jpg deleted file mode 100644 index 9fb10c3295847785c962d63d7d17e3956e442d7f..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70413-225235.jpg and /dev/null differ diff --git a/Screenshots/S70417-010650.jpg b/Screenshots/S70417-010650.jpg deleted file mode 100644 index 40b2cba5bfd1fe849cb437bf6a58d0831eeceec5..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70417-010650.jpg and /dev/null differ diff --git a/Screenshots/S70628-230542.jpg b/Screenshots/S70628-230542.jpg deleted file mode 100644 index 523758f53119a809c944883d6ba686c6d553dc17..0000000000000000000000000000000000000000 Binary files a/Screenshots/S70628-230542.jpg and /dev/null differ diff --git a/Screenshots/screenshots0.PNG b/Screenshots/screenshots0.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7a001510e8b236b4b1b564b822fff7f261db3035 Binary files /dev/null and b/Screenshots/screenshots0.PNG differ diff --git a/Screenshots/screenshots1.PNG b/Screenshots/screenshots1.PNG new file mode 100644 index 0000000000000000000000000000000000000000..61a75c1f533e5c0bcf517b1e7bf16c429764649d Binary files /dev/null and b/Screenshots/screenshots1.PNG differ diff --git a/Screenshots/screenshots2.PNG b/Screenshots/screenshots2.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e586e2bb5b08a5d6f5bf18aef8ccb18514d46a14 Binary files /dev/null and b/Screenshots/screenshots2.PNG differ diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 597a3267384a654f5538bcdf15304b8f192ba2e4..0000000000000000000000000000000000000000 --- a/app/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" - defaultConfig { - applicationId "com.wordplat.InteractiveKLineView.example" - minSdkVersion 16 - targetSdkVersion 25 - versionCode APP_VERSION_CODE as int // 这里的值去 gradle.properties 文件中修改,免去每次更新版本都 sync 一遍 - versionName APP_VERSION_NAME as String - vectorDrawables.useSupportLibrary = true - - multiDexEnabled true - - ndk { - abiFilters 'armeabi', 'armeabi-v7a', 'x86' - } - - manifestPlaceholders = [ - SERVER_HTTP_URL_VERSION: SERVER_HTTP_URL_VERSION as String, - ] - } - applicationVariants.all { variant -> - if (variant.buildType.name.contains('release')) { - variant.outputs.each { output -> - def outputFile = output.outputFile - if (outputFile != null && outputFile.name.endsWith('.apk')) { - def fileName = "ikvStockChart-" + defaultConfig.versionName + "-release.apk" - output.outputFile = new File(outputFile.parent, fileName) - } - } - } - } - aaptOptions { - additionalParameters "--no-version-vectors" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - dexOptions { - javaMaxHeapSize "4g" - } - lintOptions { - abortOnError false - } -} - -repositories { - flatDir { - dirs 'libs' - } -} - -dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - testCompile 'junit:junit:4.12' - compile 'com.android.support:support-v4:25.1.0' - compile 'com.android.support:appcompat-v7:25.1.0' - compile 'com.android.support:design:25.1.0' - compile 'com.android.support:recyclerview-v7:25.1.0' - - compile 'org.xutils:xutils:3.3.36' - // 设置状态栏颜色 - compile 'com.readystatesoftware.systembartint:systembartint:1.0.3' - // multidex - compile 'com.android.support:multidex:1.0.1' - // 下拉刷新, 支持ScrollView、RecyclerView - compile 'com.chanven.lib:cptr:1.1.0' - - compile project(':ikvStockChart') - - compile 'com.wordplat:RecyclerViewEasyDivider:1.0.4' - - compile 'io.reactivex:rxjava:1.1.9' - compile 'io.reactivex:rxandroid:1.2.1' - compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.3@aar' - - compile 'com.alibaba:fastjson:1.1.46.android' -} \ No newline at end of file diff --git a/app/libs/gson-2.2.1.jar b/app/libs/gson-2.2.1.jar deleted file mode 100644 index 93ac6d70b3ee93be63f2f51b34c8c8bac0777292..0000000000000000000000000000000000000000 Binary files a/app/libs/gson-2.2.1.jar and /dev/null differ diff --git a/app/libs/picasso-release-2.5.2.4b.aar b/app/libs/picasso-release-2.5.2.4b.aar deleted file mode 100644 index 29a752b7e13aa5f713ecc60b5a058891d292c058..0000000000000000000000000000000000000000 Binary files a/app/libs/picasso-release-2.5.2.4b.aar and /dev/null differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 0b967251a816a33beeb7b16f4daca951f92f97a9..0000000000000000000000000000000000000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,25 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/afon/Share/android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 10bae4e40795c4b30ba704d243218c568ea7d571..0000000000000000000000000000000000000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/activity/BaseActivity.java b/app/src/main/java/com/wordplat/quickstart/activity/BaseActivity.java deleted file mode 100644 index 553ec053e8354338bfdd6c7b30758a9b3699d3fc..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/BaseActivity.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.app.Activity; -import android.graphics.Color; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; - -import com.readystatesoftware.systembartint.SystemBarTintManager; -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.app.AppRuntimeInitializer; - -import org.xutils.x; - -public class BaseActivity extends AppCompatActivity { - protected Activity mActivity; // 给子类用的,避免使用 XXXActivity.this 这种写法 - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - init(); - } - - private void init() { - x.view().inject(this); - - mActivity = this; - setStatusBarColor(R.color.colorPrimaryDark); - } - - protected void setStatusBarColor(int colorResId) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && - Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - SystemBarTintManager tintManager = new SystemBarTintManager(this); - tintManager.setStatusBarTintEnabled(true); - tintManager.setNavigationBarTintEnabled(true); - tintManager.setStatusBarTintResource(colorResId); - } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getWindow().setStatusBarColor(getResources().getColor(colorResId)); - } - } - - protected void setTranslucentStatus(boolean on) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 我不知道为什么要加这个 - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 5.0 全透明实现 - - Window window = getWindow(); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(Color.TRANSPARENT); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 4.4 全透明状态栏 - - Window window = getWindow(); - WindowManager.LayoutParams winParams = window.getAttributes(); - final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; - if (on) { - winParams.flags |= bits; - } else { - winParams.flags &= ~bits; - } - window.setAttributes(winParams); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - AppRuntimeInitializer.INSTANCE.dealOnRequestPermissionsResult(this, requestCode, permissions, grantResults); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/Disable_Left_And_Right_Refresh_Activity.java b/app/src/main/java/com/wordplat/quickstart/activity/Disable_Left_And_Right_Refresh_Activity.java deleted file mode 100644 index c3e226df7569ac5926bd8988dd6b493d60bdced6..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/Disable_Left_And_Right_Refresh_Activity.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -/** - *

Disable_Left_And_Right_Refresh_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -public class Disable_Left_And_Right_Refresh_Activity extends Enable_Left_And_Right_Refresh_Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - kLineLayout.getKLineView().setEnableLeftRefresh(false); - kLineLayout.getKLineView().setEnableRightRefresh(false); - } - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, Disable_Left_And_Right_Refresh_Activity.class); - return intent; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/MainActivity.java b/app/src/main/java/com/wordplat/quickstart/activity/MainActivity.java deleted file mode 100644 index 426a7774fcde2f24ddcba171a9f6f990a2c3e4de..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/MainActivity.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.KeyEvent; -import android.widget.Toast; - -import com.wordplat.easydivider.RecyclerViewCornerRadius; -import com.wordplat.easydivider.RecyclerViewLinearDivider; -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.adapter.TextAdapter; -import com.wordplat.quickstart.utils.AppUtils; - -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; - -@ContentView(R.layout.activity_main) -public class MainActivity extends BaseActivity { - private static final String TAG = "MainActivity"; - - @ViewInject(R.id.textList) private RecyclerView textList = null; - - private TextAdapter textAdapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - textAdapter = new TextAdapter(mActivity); - textList.setLayoutManager(new LinearLayoutManager(mActivity)); - textList.setAdapter(textAdapter); - - RecyclerViewCornerRadius cornerRadius = new RecyclerViewCornerRadius(textList); - cornerRadius.setCornerRadius(AppUtils.dpTopx(mActivity, 10)); - - RecyclerViewLinearDivider linearDivider = new RecyclerViewLinearDivider(mActivity, LinearLayoutManager.VERTICAL); - linearDivider.setDividerSize(1); - linearDivider.setDividerColor(0xff888888); - linearDivider.setDividerMargin(AppUtils.dpTopx(mActivity, 10), AppUtils.dpTopx(mActivity, 10)); - linearDivider.setDividerBackgroundColor(0xffffffff); - linearDivider.setShowHeaderDivider(false); - linearDivider.setShowFooterDivider(false); - - // 圆角背景必须第一个添加 - textList.addItemDecoration(cornerRadius); - textList.addItemDecoration(linearDivider); - - for (int i = 0 ; i < 16 ; i++) { - String result = Integer.toHexString(255 - i * 12); - Log.i(TAG, "##d onCreate: " + result); - } - } - - /** - * 点击两次退出 - */ - private long exitTime = 0; - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - exit(); - return false; - } - return super.onKeyDown(keyCode, event); - } - - private void exit() { - if ((System.currentTimeMillis() - exitTime) > 2000) { - Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show(); - exitTime = System.currentTimeMillis(); - } else { - finish(); - System.exit(0); - } - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/Multi_Color_Dynamic_Change_Configuration_Activity.java b/app/src/main/java/com/wordplat/quickstart/activity/Multi_Color_Dynamic_Change_Configuration_Activity.java deleted file mode 100644 index cc53b1a502fa91096e3df87cd35d43a3f2fc19de..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/Multi_Color_Dynamic_Change_Configuration_Activity.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -/** - *

Multi_Color_Dynamic_Change_Configuration_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -public class Multi_Color_Dynamic_Change_Configuration_Activity extends BaseActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // TODO: 2017/4/11 - } - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, Multi_Color_Dynamic_Change_Configuration_Activity.class); - return intent; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/With_Fragment_And_TabLayout_Switcher_Example_Activity.java b/app/src/main/java/com/wordplat/quickstart/activity/With_Fragment_And_TabLayout_Switcher_Example_Activity.java deleted file mode 100644 index 46b6f8b8675636e7474023d52f83a975a3305b6c..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/With_Fragment_And_TabLayout_Switcher_Example_Activity.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; - -import com.chanven.lib.cptr.PtrDefaultHandler; -import com.chanven.lib.cptr.PtrFrameLayout; -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.fragment.KLineFragment; -import com.wordplat.quickstart.mvp.StockPresenter; -import com.wordplat.quickstart.widget.pulllistview.PullListLayout; - -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; - -/** - *

With_Fragment_And_TabLayout_Switcher_Example_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -@ContentView(R.layout.activity_with_fragment_and_tablayout_switcher) -public class With_Fragment_And_TabLayout_Switcher_Example_Activity extends BaseActivity { - - @ViewInject(R.id.tabLayout) private TabLayout tabLayout = null; - @ViewInject(R.id.pullListLayout) private PullListLayout pullListLayout = null; - - private KLineFragment dayKLineFragment; - private KLineFragment weekKLineFragment; - private KLineFragment monthKLineFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - initUI(); - } - - private void initUI() { - pullListLayout.setPtrHandler(ptrHandler); - - dayKLineFragment = KLineFragment.newInstance(StockPresenter.KLineType.DAY); - weekKLineFragment = KLineFragment.newInstance(StockPresenter.KLineType.WEEK); - monthKLineFragment = KLineFragment.newInstance(StockPresenter.KLineType.MONTH); - - final String[] tabStrings = new String[]{"日K", "周K", "月K"}; - - tabLayout.removeAllTabs(); - for (int i = 0; i < tabStrings.length; i++) { - TabLayout.Tab tab = tabLayout.newTab(); - tab.setText(tabStrings[i]); - tabLayout.addTab(tab); - } - tabLayout.addOnTabSelectedListener(onTabSelectedListener); - - addFragment(dayKLineFragment); - } - - /////////////////////////////////////////////////////////////////////////// - // fragment 显示隐藏 - /////////////////////////////////////////////////////////////////////////// - - private void hideAllFragment() { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - if (!dayKLineFragment.isHidden()) { - ft.hide(dayKLineFragment); - } - if (!weekKLineFragment.isHidden()) { - ft.hide(weekKLineFragment); - } - if (!monthKLineFragment.isHidden()) { - ft.hide(monthKLineFragment); - } - ft.commit(); - } - - private void showFragment(Fragment fragment) { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.show(fragment); - ft.commit(); - } - - private void addFragment(Fragment fragment) { - if (!fragment.isAdded()) { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.add(R.id.kLineContent, fragment); - ft.commit(); - } - } - - private void switchChart(int position) { - switch (position) { - case 0: - showFragment(dayKLineFragment); - break; - - case 1: - addFragment(weekKLineFragment); - showFragment(weekKLineFragment); - break; - - case 2: - addFragment(monthKLineFragment); - showFragment(monthKLineFragment); - break; - } - } - - private TabLayout.OnTabSelectedListener onTabSelectedListener = new TabLayout.OnTabSelectedListener() { - @Override - public void onTabSelected(TabLayout.Tab tab) { - hideAllFragment(); - switchChart(tab.getPosition()); - } - @Override - public void onTabUnselected(TabLayout.Tab tab) {} - @Override - public void onTabReselected(TabLayout.Tab tab) {} - }; - - /////////////////////////////////////////////////////////////////////////// - // 结束:fragment 显示隐藏 - /////////////////////////////////////////////////////////////////////////// - - private PtrDefaultHandler ptrHandler = new PtrDefaultHandler() { - @Override - public void onRefreshBegin(PtrFrameLayout frame) { - pullListLayout.postDelayed(new Runnable() { - @Override - public void run() { - pullListLayout.refreshComplete(); - } - }, 2000); - } - }; - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, With_Fragment_And_TabLayout_Switcher_Example_Activity.class); - return intent; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/With_Pull_To_Refresh_Example_Activity.java b/app/src/main/java/com/wordplat/quickstart/activity/With_Pull_To_Refresh_Example_Activity.java deleted file mode 100644 index 4ada84c2cbbcb2bcfff37ffdeb768420f1ab457c..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/With_Pull_To_Refresh_Example_Activity.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import com.chanven.lib.cptr.PtrDefaultHandler; -import com.chanven.lib.cptr.PtrFrameLayout; -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.widget.pulllistview.PullListLayout; - -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; - -/** - *

With_Pull_To_Refresh_Example_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -@ContentView(R.layout.activity_with_pull_to_refresh) -public class With_Pull_To_Refresh_Example_Activity extends Enable_Left_And_Right_Refresh_Activity { - - @ViewInject(R.id.pullListLayout) private PullListLayout pullListLayout = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - initUI(); - } - - private void initUI() { - pullListLayout.setPtrHandler(ptrHandler); - } - - private PtrDefaultHandler ptrHandler = new PtrDefaultHandler() { - @Override - public void onRefreshBegin(PtrFrameLayout frame) { - pullListLayout.postDelayed(new Runnable() { - @Override - public void run() { - pullListLayout.refreshComplete(); - } - }, 2000); - } - }; - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, With_Pull_To_Refresh_Example_Activity.class); - return intent; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/With_RecyclerView_Example_Activity.java b/app/src/main/java/com/wordplat/quickstart/activity/With_RecyclerView_Example_Activity.java deleted file mode 100644 index 4b73bd78cd348b6c443938dad7e72567962ef356..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/activity/With_RecyclerView_Example_Activity.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -/** - *

With_RecyclerView_Example_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -public class With_RecyclerView_Example_Activity extends BaseActivity { - - private static final String TAG = "Activity"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // TODO: 2017/4/11 - } - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, With_RecyclerView_Example_Activity.class); - return intent; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/adapter/BaseViewHolder.java b/app/src/main/java/com/wordplat/quickstart/adapter/BaseViewHolder.java deleted file mode 100644 index 0f6975181ab97717f4bf26a1f38ac581048d0964..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/adapter/BaseViewHolder.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.wordplat.quickstart.adapter; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import org.xutils.x; - -/** - * Created by afon on 2017/2/5. - */ - -public abstract class BaseViewHolder extends RecyclerView.ViewHolder { - - protected Context mContext; - - public BaseViewHolder(View itemView) { - super(itemView); - - mContext = itemView.getContext(); - - x.view().inject(this, itemView); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java b/app/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java deleted file mode 100644 index 23c0c1cce69187931f69cf093e496c01d7d31afe..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.wordplat.quickstart.adapter; - -import android.app.Activity; -import android.content.Intent; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.activity.Disable_Left_And_Right_Refresh_Activity; -import com.wordplat.quickstart.activity.Enable_Left_And_Right_Refresh_Activity; -import com.wordplat.quickstart.activity.MACD_RSI_KDJ_Show_Together_Activity; -import com.wordplat.quickstart.activity.Multi_Color_Dynamic_Change_Configuration_Activity; -import com.wordplat.quickstart.activity.Simple_TimeLine_Example_Activity; -import com.wordplat.quickstart.activity.With_Fragment_And_TabLayout_Switcher_Example_Activity; -import com.wordplat.quickstart.activity.With_Pull_To_Refresh_Example_Activity; -import com.wordplat.quickstart.activity.With_RecyclerView_Example_Activity; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by afon on 2017/2/10. - */ - -public class TextAdapter extends RecyclerView.Adapter { - - private static List textList = new ArrayList<>(); - - static { - textList.add("左滑右滑加载(Enable left and right refresh)"); - textList.add("禁用左滑右滑加载"); - textList.add("多个指标共同显示、联动(MACD, RSI, KDJ)"); - textList.add("在Fragment中使用"); - textList.add("带有下拉刷新的需求中使用"); -// textList.add("动态改变颜色、尺寸配置"); // 还没有准备好 -// textList.add("在 RecyclerView 列表中使用"); // 还没有准备好 - textList.add("简单分时图"); - } - - private Activity mActivity; - - public TextAdapter(Activity activity) { - mActivity = activity; - } - - @Override - public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from(mActivity).inflate(R.layout.item_text, parent, false); - - return new TextViewHolder(itemView); - } - - @Override - public void onBindViewHolder(TextViewHolder holder, final int position) { - holder.text.setText(textList.get(position)); - holder.text.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = null; - switch (position) { - case 0: - intent = Enable_Left_And_Right_Refresh_Activity.createIntent(mActivity); - break; - - case 1: - intent = Disable_Left_And_Right_Refresh_Activity.createIntent(mActivity); - break; - - case 2: - intent = MACD_RSI_KDJ_Show_Together_Activity.createIntent(mActivity); - break; - - case 3: - intent = With_Fragment_And_TabLayout_Switcher_Example_Activity.createIntent(mActivity); - break; - - case 4: - intent = With_Pull_To_Refresh_Example_Activity.createIntent(mActivity); - break; - -// case 5: -// intent = Multi_Color_Dynamic_Change_Configuration_Activity.createIntent(mActivity); -// break; - -// case 6: -// intent = With_RecyclerView_Example_Activity.createIntent(mActivity); -// break; - - case 5: - intent = Simple_TimeLine_Example_Activity.createIntent(mActivity); - break; - - default: - break; - } - - if (intent != null) { - mActivity.startActivity(intent); - } - } - }); - } - - @Override - public int getItemCount() { - return textList.size(); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/adapter/TextViewHolder.java b/app/src/main/java/com/wordplat/quickstart/adapter/TextViewHolder.java deleted file mode 100644 index 7b060a3a0f05ed94c191018c6e11644425adea23..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/adapter/TextViewHolder.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wordplat.quickstart.adapter; - -import android.view.View; -import android.widget.TextView; - -import com.wordplat.quickstart.R; - -import org.xutils.view.annotation.ViewInject; - -/** - * Created by afon on 2017/2/10. - */ - -public class TextViewHolder extends BaseViewHolder { - - @ViewInject(R.id.text) TextView text; - - public TextViewHolder(View itemView) { - super(itemView); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/app/App.java b/app/src/main/java/com/wordplat/quickstart/app/App.java deleted file mode 100644 index 05b85ece697a47e86346417b8568dac77c43c37a..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/app/App.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.wordplat.quickstart.app; - -import android.app.ActivityManager; -import android.app.Application; -import android.content.Context; -import android.support.multidex.MultiDex; -import com.wordplat.quickstart.BuildConfig; - -import org.xutils.x; - -/** - * Created by afon on 2017/1/24. - */ - -public class App extends Application { - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - MultiDex.install(this); - } - - @Override - public void onCreate() { - super.onCreate(); - - if (BuildConfig.APPLICATION_ID.equals(getCurProcessName(this))) { - AppRuntimeInitializer.INSTANCE.initRuntime(this); - } - - x.Ext.init(this); - } - - private String getCurProcessName(Context context) { - int pid = android.os.Process.myPid(); - ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - for (ActivityManager.RunningAppProcessInfo appProcess : activityManager.getRunningAppProcesses()) { - if (appProcess.pid == pid) { - return appProcess.processName; - } - } - return ""; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/app/AppRuntime.java b/app/src/main/java/com/wordplat/quickstart/app/AppRuntime.java deleted file mode 100644 index 671886887896b0412452da1dade8ad058bc5b5ed..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/app/AppRuntime.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.wordplat.quickstart.app; - -import android.app.Activity; -import android.content.Context; - -import java.lang.ref.WeakReference; -import java.util.LinkedList; - -/** - * Created by afon on 2017/1/24. - */ - -public class AppRuntime { - - public static Context sContext; // 静态类型的 Context,你懂的 - public static LinkedList sActivities = new LinkedList<>(); // 查找当前的 Activity 栈用的 - public static WeakReference sActivityStops = new WeakReference<>(null); // 快速查找位于栈顶的 Activity 用的 - - public static String serverVersion = "1.0"; // 与服务器通信接口版本号。本值会自动读取到 gradle.properties 文件中定义的,不需要手动修改 * - public static String appVersion = "1.0.0"; // 本APP的版本号。本值会自动读取到 AndroidManifest.xml 文件中定义的,不需要手动修改 * -} diff --git a/app/src/main/java/com/wordplat/quickstart/app/AppRuntimeInitializer.java b/app/src/main/java/com/wordplat/quickstart/app/AppRuntimeInitializer.java deleted file mode 100644 index d268691bcf6fcde3a90667ed639fd2774a6b7ad8..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/app/AppRuntimeInitializer.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.wordplat.quickstart.app; - -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; - -import com.wordplat.quickstart.activity.MainActivity; -import com.wordplat.quickstart.utils.AppUtils; - -import java.lang.ref.WeakReference; - -/** - * APP 初始化程序 - * - * 用于 APP 运行时环境配置 - * - * Created by afon on 2016/12/20. - */ - -public enum AppRuntimeInitializer { - - INSTANCE; - - private AppRuntimeInitializer() { - - } - - public void initRuntime(Application application) { - final String TAG = "AppRuntime"; - - AppRuntime.sContext = application.getApplicationContext(); - - application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - AppRuntime.sActivities.add(activity); - - if (activity instanceof MainActivity) { - requestIMEIforMIfNeeded(activity); - } - } - - @Override - public void onActivityStarted(Activity activity) { - - } - - @Override - public void onActivityResumed(Activity activity) { - AppRuntime.sActivityStops = new WeakReference<>(activity); - } - - @Override - public void onActivityPaused(Activity activity) { - } - - @Override - public void onActivityStopped(Activity activity) { - AppRuntime.sActivityStops = new WeakReference<>(null); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - - } - - @Override - public void onActivityDestroyed(Activity activity) { - AppRuntime.sActivities.remove(activity); - } - }); - - initVersion(application); - initIMEI(); - } - - private void initVersion(Application application) { - final String TAG = "AppRuntime"; - - try { - ApplicationInfo appInfo = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA); - AppRuntime.serverVersion = appInfo.metaData.getString("server_http_url_version"); - if(AppRuntime.serverVersion.startsWith("ver")) { - AppRuntime.serverVersion = AppRuntime.serverVersion.substring(3); - } - AppRuntime.appVersion = AppUtils.getAppVersionName(application); - } catch (Exception e) { - e.printStackTrace(); - } - - Log.e(TAG, "### initVersion: serverVersion = " + AppRuntime.serverVersion + ", appVersion = " + AppRuntime.appVersion); - } - - private void initIMEI() { - final String TAG = "AppRuntime"; - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(AppRuntime.sContext, - android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { - TelephonyManager tm = (TelephonyManager) AppRuntime.sContext.getSystemService(Context.TELEPHONY_SERVICE); - DeviceRuntime.IMEI = tm.getDeviceId(); - DeviceRuntime.DEVICE_ID = Settings.Secure.getString(AppRuntime.sContext.getContentResolver(), Settings.Secure.ANDROID_ID); - } - - Log.e(TAG, "### initIMEI: IMEI = " + DeviceRuntime.IMEI + ", DEVICE_ID = " + DeviceRuntime.DEVICE_ID); - } - - private static final int REQUEST_IMEI_PERMISSION = 1346; - - /** Android 6.0 以上系统请求 IMEI 和设备 ID */ - private void requestIMEIforMIfNeeded(Activity activity) { - if (ContextCompat.checkSelfPermission(AppRuntime.sContext, - android.Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(activity, new String[]{android.Manifest.permission.READ_PHONE_STATE}, REQUEST_IMEI_PERMISSION); - } - } - - /** 处理权限请求成功之后回调 */ - public void dealOnRequestPermissionsResult(Context context, int requestCode, String[] permissions, int[] grantResults) { - switch (requestCode) { - case REQUEST_IMEI_PERMISSION: - if (grantResults.length == 1) { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - initIMEI(); - } - } - break; - - default: - break; - } - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/bean/request/ServerRequestParams.java b/app/src/main/java/com/wordplat/quickstart/bean/request/ServerRequestParams.java deleted file mode 100644 index 94a65f4f0ece5696b39607ff4a136e328acb594f..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/bean/request/ServerRequestParams.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.wordplat.quickstart.bean.request; - -import android.text.TextUtils; -import android.util.Log; - -import com.alibaba.fastjson.JSONObject; -import com.wordplat.quickstart.BuildConfig; -import com.wordplat.quickstart.app.AppRuntime; -import com.wordplat.quickstart.utils.SSLHelper; - -import org.xutils.http.RequestParams; - -import java.io.File; - -import javax.net.ssl.SSLSocketFactory; - -/** - *

ServerRequestParams

- *

Date: 2017/4/5

- * - * @author afon - */ - -public class ServerRequestParams extends RequestParams { - private static final String TAG = "ServerRequestParams"; - - private JSONObject params = null; - - private JSONObject requestJson= null; - - private String uploadKey = null; - private Object uploadValue = null; - - public ServerRequestParams(String URL) { - super(URL); - params = new JSONObject(); - - requestJson = new JSONObject(); - - setMaxRetryCount(2); - setConnectTimeout(20000); - - SSLSocketFactory sslSocketFactory = SSLHelper.getSSLSocketFactory(AppRuntime.sContext); - if (sslSocketFactory != null) { - setSslSocketFactory(sslSocketFactory); - } - } - - public void addRequestParams(String key, Object value) { - if (TextUtils.isEmpty(key) || value == null) { - return; - } - - requestJson.put(key, value); - } - - public void addCustomParams(String key, JSONObject jsonObject) { - if (TextUtils.isEmpty(key) || jsonObject == null) { - return; - } - - params.put(key, jsonObject); - } - - /** - * 上传文件 - * - * @param value 可以是File, InputStream 或 byte[] - */ - public void upload(Object value) { - uploadKey = "uploadFile"; - uploadValue = value; - } - - /** - * 添加完参数或设置参数后调用此方法 - */ - public void commit() { - params.put("RequestParams", requestJson); - - if (BuildConfig.DEBUG) { - Log.i(TAG, "##d 请求数据: " + requestJson.toJSONString()); - } - - addBodyParameter("", requestJson.toJSONString()); - - if (!TextUtils.isEmpty(uploadKey) && uploadValue != null) { - if (uploadValue instanceof org.json.JSONArray) { - addParameter(uploadKey, uploadValue); - - } else if(uploadValue instanceof String) { - addBodyParameter(uploadKey, new File((String) uploadValue)); - - } else if(uploadValue instanceof File) { - addBodyParameter(uploadKey, (File)uploadValue); - - } else { - addBodyParameter(uploadKey, uploadValue, "image/jpeg", System.currentTimeMillis()+".jpg"); - } - } - } - - @Override - public String toString() { - return params.toJSONString(); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponse.java b/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponse.java deleted file mode 100644 index f65190e3b88a13b747c67e5cace1f9e2c18a926b..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.wordplat.quickstart.bean.response; - -import org.xutils.http.annotation.HttpResponse; - -/** - *

ServerResponse

- *

Date: 2017/4/11

- * - * @author afon - */ - -@HttpResponse(parser = ServerResponseParser.class) -public class ServerResponse implements IResultResponse { - - @Override - public int getResultCode() { - return 0; - } - - @Override - public String getResultDescr() { - return null; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponseParser.java b/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponseParser.java deleted file mode 100644 index a14dba4806fa9b188c80028e679098b0eaadee7a..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/bean/response/ServerResponseParser.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.wordplat.quickstart.bean.response; - -import android.util.Log; - -import com.alibaba.fastjson.JSON; -import com.wordplat.quickstart.BuildConfig; - -import org.xutils.http.app.ResponseParser; -import org.xutils.http.request.UriRequest; - -import java.lang.reflect.Type; - -/** - *

ServerResponseParser

- *

Date: 2017/4/11

- * - * @author afon - */ - -public class ServerResponseParser implements ResponseParser { - private static final String TAG = "ServerResponseParser"; - - @Override - public void checkResponse(UriRequest request) throws Throwable { - - } - - @Override - public Object parse(Type resultType, Class resultClass, String result) throws Throwable { - if (BuildConfig.DEBUG) { - Log.i(TAG, "##d 服务器返回数据:" + result); - } - - return JSON.parseObject(result, resultClass); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/fragment/BaseFragment.java b/app/src/main/java/com/wordplat/quickstart/fragment/BaseFragment.java deleted file mode 100644 index 2548d930d1256fa8b36be1e56c7c9c918365a7bd..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/fragment/BaseFragment.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.wordplat.quickstart.fragment; - -import android.app.Activity; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.xutils.x; - -public class BaseFragment extends Fragment { - protected Activity mActivity; // 给子类用的 - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mActivity = getActivity(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return x.view().inject(this, inflater, container); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaApiRequest.java b/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaApiRequest.java deleted file mode 100644 index b2802ac8ebfebfadcf5f5b596f134b5599527719..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaApiRequest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.wordplat.quickstart.mvp; - -import android.util.Log; - -import com.wordplat.quickstart.BuildConfig; -import com.wordplat.quickstart.bean.BtcBean; - -import org.xutils.http.RequestParams; - -import java.util.List; -import java.util.Locale; - -import rx.Observable; -import rx.Subscriber; -import rx.schedulers.Schedulers; - -/** - *

BtcChinaApiRequest

- *

Date: 2017/4/16

- * - * @author afon - */ - -public class BtcChinaApiRequest extends BaseRequest { - private static final String TAG = "BtcChinaApiRequest"; - - private static final String URL = "https://data.btcchina.com/data/historydata?since=%d&limit=%d"; - - public static Observable> getHistoryData(final int id, final int limit) { - return Observable.create(new Observable.OnSubscribe>() { - @Override - public void call(Subscriber> subscriber) { - String url = String.format(Locale.ENGLISH, - URL, - id, - limit); - - if (BuildConfig.DEBUG) { - Log.d(TAG, "##d 请求参数: " + url); - } - - requestArray(subscriber, new RequestParams(url), BtcBean.class); - } - }).subscribeOn(Schedulers.io()); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java b/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java deleted file mode 100644 index 60301070871dee5c63fdf3a3a734ddc1a80be9b6..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.wordplat.quickstart.mvp; - -import com.wordplat.quickstart.bean.BtcBean; - -import java.util.List; - -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; - -/** - *

BtcChinaPresenter

- *

Date: 2017/4/16

- * - * @author afon - */ - -public class BtcChinaPresenter extends BasePresenter { - - private List btcList; - - public void getSimple(final int requestCode) { - baseView.onStartRequest(requestCode); - - Subscription subscription = BtcChinaApiRequest.getHistoryData(5000, 600) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1>() { - @Override - public void call(List response) { - baseView.onFinishRequest(requestCode); - if (response != null && response.size() > 0) { - btcList = response; - - baseView.onSuccess(requestCode); - } else { - baseView.onResultEmpty(requestCode); - } - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - baseView.onFinishRequest(requestCode); - handleError(requestCode, throwable); - } - }); - - addSubscription(subscription); - } - - public List getBtcList() { - return btcList; - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/StockApiRequest.java b/app/src/main/java/com/wordplat/quickstart/mvp/StockApiRequest.java deleted file mode 100644 index 4dae54e56c0f161a3c9e3e1d383535b762ae7af6..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/mvp/StockApiRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.wordplat.quickstart.mvp; - -import com.wordplat.quickstart.bean.KLineBean; -import com.wordplat.quickstart.bean.request.ServerRequestParams; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import rx.Observable; -import rx.Subscriber; -import rx.schedulers.Schedulers; - -/** - *

自己搭建的股票 API 服务器接口

- *

Date: 2017/4/11

- * - * @author afon - */ - -public class StockApiRequest extends BaseRequest { - private static final String TAG = "StockApiRequest"; - - private static final String URL = "https://api.wordplat.com/ts/v1/get_k_data/%s"; - - private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd"); - - - public static Observable> getKLine(final String stockCode, - final Date startDate, - final Date endData, - final String type) { - return Observable.create(new Observable.OnSubscribe>() { - @Override - public void call(Subscriber> subscriber) { - String url = String.format(Locale.ENGLISH, URL, stockCode); - - ServerRequestParams requestParams = new ServerRequestParams(url); - requestParams.addRequestParams("start", sDateFormat.format(startDate)); - requestParams.addRequestParams("end", sDateFormat.format(endData)); - requestParams.addRequestParams("ktype", type); - requestParams.commit(); - - requestArray(subscriber, requestParams, KLineBean.class); - } - }).subscribeOn(Schedulers.io()); - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/utils/AppUtils.java b/app/src/main/java/com/wordplat/quickstart/utils/AppUtils.java deleted file mode 100644 index ac0943f6ca47035815c2abad0dcb7bd95104c381..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/utils/AppUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.wordplat.quickstart.utils; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; - -/** - * APP工具 - * - * @author liutao - */ - -public class AppUtils { - - /** - * 网络是否连接 - * - * @param context - */ - public static boolean isConnected(Context context) { - ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = connManager.getActiveNetworkInfo(); - - return netInfo == null || !netInfo.isConnected() ? false : true; - } - - /** - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) - */ - public static int dpTopx(Context context, float dpValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); - } - - /** - * 根据手机的分辨率从 px(像素) 的单位 转成为 dp - */ - public static int pxTodp(Context context, float pxValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (pxValue / scale + 0.5f); - } - - /** - * 获取App版本号 - */ - public static String getAppVersionName(Context context) { - String packageName = null; - String versionName = null; - int versionCode = 0; - try { - packageName = context.getPackageName(); - - versionName = context.getPackageManager().getPackageInfo(packageName, 0).versionName; - - versionCode = context.getPackageManager().getPackageInfo(packageName, 0).versionCode; - - } catch (Exception e) { - e.printStackTrace(); - } - return versionName; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/widget/TabButton.java b/app/src/main/java/com/wordplat/quickstart/widget/TabButton.java deleted file mode 100644 index e4f2a17e6a1c7f2b0ac2849052bf33dd533f6939..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/widget/TabButton.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.wordplat.quickstart.widget; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.RadioButton; - -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.utils.AppUtils; - -/** - * 带提示红点的Tab按键 - * - * @author liutao - */ -public class TabButton extends RadioButton { - private int radius = 0; - private int offset = 0; - private Paint paint = null; - private boolean isShow = false; - - private int topDrawableWidth = 0; - - public TabButton(Context context) { - this(context, null); - } - - public TabButton(Context context, AttributeSet attrs) { - this(context, attrs, android.R.attr.radioButtonStyle); - } - - public TabButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setButtonDrawable(null); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabButton); - radius = (int) a.getDimension(R.styleable.TabButton_dotSize, AppUtils.dpTopx(context, 4)); - a.recycle(); - - paint = new Paint(); - paint.setColor(Color.RED); - paint.setAntiAlias(true); - offset = radius * 2; - - Drawable[] drawables = null; - try { - drawables = getCompoundDrawables(); - - if(drawables == null || drawables[1] == null){ - return; - } - - topDrawableWidth = drawables[1].getIntrinsicWidth(); - }catch (Exception e) { - e.printStackTrace(); - }finally { - drawables = null; - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - if(isShow) { - canvas.drawCircle((getWidth()/2+topDrawableWidth/2), offset, radius, paint); - } - } - - public void setShowTag(boolean isShow) { - this.isShow = isShow; - invalidate(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/ListRefreshView.java b/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/ListRefreshView.java deleted file mode 100644 index 8afe35395f231c3a8880758d27492dd90d94aa12..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/ListRefreshView.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.wordplat.quickstart.widget.pulllistview; - -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.AnimationDrawable; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.chanven.lib.cptr.PtrFrameLayout; -import com.chanven.lib.cptr.PtrUIHandler; -import com.chanven.lib.cptr.indicator.PtrIndicator; -import com.wordplat.quickstart.R; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * 下拉列表头部布局 - * - * Created by liutao on 16/9/6. - */ - -public class ListRefreshView extends FrameLayout implements PtrUIHandler { - private TextView statusHint = null; - private TextView updateTime = null; - private ImageView loadingImage = null; - - private String UPDATE_TIME_KEY = null; - private String mLastUpdateTimeKey; - private SimpleDateFormat sDataFormat = null; - private LastUpdateTimeUpdater mLastUpdateTimeUpdater = null; - private long mLastUpdateTime = -1; - private boolean mShouldShowLastUpdate; - - public ListRefreshView(Context context) { - this(context, null); - } - - public ListRefreshView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ListRefreshView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initUI(); - } - - private void initUI() { - View view = LayoutInflater.from(getContext()).inflate(R.layout.view_list_refresh, null); - statusHint = (TextView) view.findViewById(R.id.list_refresh_status); - updateTime = (TextView) view.findViewById(R.id.list_refresh_updateTime); - loadingImage = (ImageView) view.findViewById(R.id.list_refresh_loading_image); - loadingImage.setVisibility(GONE); - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - addView(view); - - sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - mLastUpdateTimeUpdater = new LastUpdateTimeUpdater(); - try { - mLastUpdateTimeKey = ((Activity)getContext()).getLocalClassName(); - UPDATE_TIME_KEY = getContext().getPackageName(); - }catch (Exception e) { - e.printStackTrace(); - } - } - - public void setStatusHintColor(int colorResId) { - statusHint.setTextColor(getResources().getColor(colorResId)); - } - - public void setUpdateTimeColor(int colorResId) { - updateTime.setTextColor(getResources().getColor(colorResId)); - } - -// public void setRefreshAnimRes(int refreshImageAnimRes) { -// loadingImage.setBackgroundResource(refreshImageAnimRes); -// } - - @Override - public void onUIReset(PtrFrameLayout frame) { - mShouldShowLastUpdate = true; - updateLastTime(); - } - - @Override - public void onUIRefreshPrepare(PtrFrameLayout frame) { - mShouldShowLastUpdate = true; - updateLastTime(); - mLastUpdateTimeUpdater.start(); - crossRotateLineFromBottomUnderTouch(frame); - } - - @Override - public void onUIRefreshBegin(PtrFrameLayout frame) { - mShouldShowLastUpdate = false; - statusHint.setText(com.chanven.lib.cptr.R.string.cube_ptr_refreshing); - updateLastTime(); - mLastUpdateTimeUpdater.stop(); - loadingImage.setVisibility(VISIBLE); - ((Animatable) loadingImage.getDrawable()).start(); - } - - @Override - public void onUIRefreshComplete(PtrFrameLayout frame) { - statusHint.setText(com.chanven.lib.cptr.R.string.cube_ptr_refresh_complete); - SharedPreferences sharedPreferences = getContext().getSharedPreferences(UPDATE_TIME_KEY, 0); - if (!TextUtils.isEmpty(mLastUpdateTimeKey)) { - mLastUpdateTime = new Date().getTime(); - sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit(); - } - ((Animatable) loadingImage.getDrawable()).stop(); - loadingImage.setVisibility(GONE); - } - - @Override - public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { - final int mOffsetToRefresh = frame.getOffsetToRefresh(); - final int currentPos = ptrIndicator.getCurrentPosY(); - final int lastPos = ptrIndicator.getLastPosY(); - - if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) { - if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) { - crossRotateLineFromBottomUnderTouch(frame); - } - } else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) { - if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) { - crossRotateLineFromTopUnderTouch(frame); - } - } - } - - private void crossRotateLineFromTopUnderTouch(PtrFrameLayout frame) { - if (!frame.isPullToRefresh()) { - statusHint.setText(com.chanven.lib.cptr.R.string.cube_ptr_release_to_refresh); - } - } - - private void crossRotateLineFromBottomUnderTouch(PtrFrameLayout frame) { - if (frame.isPullToRefresh()) { - statusHint.setText(com.chanven.lib.cptr.R.string.cube_ptr_pull_down_to_refresh); - } else { - statusHint.setText(com.chanven.lib.cptr.R.string.cube_ptr_pull_down); - } - } - - private void updateLastTime() { - if (!TextUtils.isEmpty(mLastUpdateTimeKey) && !mShouldShowLastUpdate) { - String time = getLastUpdateTime(); - if (!TextUtils.isEmpty(time)) { - updateTime.setVisibility(VISIBLE); - updateTime.setText(time); - } else { - updateTime.setVisibility(GONE); - } - } - } - - private String getLastUpdateTime() { - if (mLastUpdateTime == -1 && !TextUtils.isEmpty(mLastUpdateTimeKey)) { - mLastUpdateTime = getContext().getSharedPreferences(UPDATE_TIME_KEY, 0).getLong(mLastUpdateTimeKey, -1); - } - if (mLastUpdateTime == -1) { - return null; - } - long diffTime = new Date().getTime() - mLastUpdateTime; - int seconds = (int) (diffTime / 1000); - if (diffTime < 0) { - return null; - } - if (seconds <= 0) { - return null; - } - StringBuilder sb = new StringBuilder(); - sb.append("最后更新: "); - - if (seconds < 60) { - sb.append(seconds + getContext().getString(com.chanven.lib.cptr.R.string.cube_ptr_seconds_ago)); - } else { - int minutes = (seconds / 60); - if (minutes > 60) { - int hours = minutes / 60; - if (hours > 24) { - Date date = new Date(mLastUpdateTime); - sb.append(sDataFormat.format(date)); - } else { - sb.append(hours + getContext().getString(com.chanven.lib.cptr.R.string.cube_ptr_hours_ago)); - } - - } else { - sb.append(minutes + getContext().getString(com.chanven.lib.cptr.R.string.cube_ptr_minutes_ago)); - } - } - return sb.toString(); - } - - private class LastUpdateTimeUpdater implements Runnable { - private boolean mRunning = false; - private void start() { - if (TextUtils.isEmpty(mLastUpdateTimeKey)) { - return; - } - mRunning = true; - run(); - } - - private void stop() { - mRunning = false; - removeCallbacks(this); - } - - @Override - public void run() { - updateLastTime(); - if (mRunning) { - postDelayed(this, 1000); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/LoadMoreViewFooter.java b/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/LoadMoreViewFooter.java deleted file mode 100644 index 61337d3aad54a222d53c87d49af9dd9c09c58fc3..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/LoadMoreViewFooter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.wordplat.quickstart.widget.pulllistview; - -import android.view.View; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.chanven.lib.cptr.loadmore.ILoadMoreViewFactory; - -/** - * Created by afon on 2016/12/9. - */ - -public class LoadMoreViewFooter implements ILoadMoreViewFactory { - - private LoadMoreHelper loadMoreHelper; - - public LoadMoreHelper getLoadMoreView() { - return loadMoreHelper; - } - - @Override - public ILoadMoreView madeLoadMoreView() { - loadMoreHelper = new LoadMoreHelper(); - return loadMoreHelper; - } - - public static class LoadMoreHelper implements ILoadMoreView { - - protected View footerView; - protected TextView footerTv; - protected ProgressBar footerBar; - - protected View.OnClickListener onClickRefreshListener; - - @Override - public void init(FootViewAdder footViewHolder, View.OnClickListener onClickRefreshListener) { - footerView = footViewHolder.addFootView(com.chanven.lib.cptr.R.layout.loadmore_default_footer); - footerTv = (TextView) footerView.findViewById(com.chanven.lib.cptr.R.id.loadmore_default_footer_tv); - footerBar = (ProgressBar) footerView.findViewById(com.chanven.lib.cptr.R.id.loadmore_default_footer_progressbar); - footerView.setClickable(true); - footerView.setFocusable(true); - footerView.setFocusableInTouchMode(true); - this.onClickRefreshListener = onClickRefreshListener; - showNormal(); - } - - @Override - public void showNormal() { - footerTv.setText("点击加载更多"); - footerBar.setVisibility(View.GONE); - footerView.setOnClickListener(onClickRefreshListener); - } - - @Override - public void showLoading() { - footerTv.setText("正在加载中..."); - footerBar.setVisibility(View.VISIBLE); - footerView.setOnClickListener(null); - } - - @Override - public void showFail(Exception exception) { - footerTv.setText("加载失败,点击重新加载"); - footerBar.setVisibility(View.GONE); - footerView.setOnClickListener(onClickRefreshListener); - } - - @Override - public void showNomore() { - footerTv.setText("已经加载完毕"); - footerBar.setVisibility(View.GONE); - footerView.setOnClickListener(null); - } - - @Override - public void setFooterVisibility(boolean isVisible) { - footerView.setVisibility(isVisible ? View.VISIBLE : View.GONE); - } - } -} diff --git a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/PullListLayout.java b/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/PullListLayout.java deleted file mode 100644 index cc7975f9e98824bc88729e73aa74a5dc89b7aa89..0000000000000000000000000000000000000000 --- a/app/src/main/java/com/wordplat/quickstart/widget/pulllistview/PullListLayout.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.wordplat.quickstart.widget.pulllistview; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; - -import com.chanven.lib.cptr.PtrFrameLayout; -import com.chanven.lib.cptr.loadmore.DefaultLoadMoreViewFooter; -import com.chanven.lib.cptr.loadmore.ILoadMoreViewFactory; -import com.wordplat.ikvstockchart.compat.GestureMoveActionCompat; - -/** - * Created by liutao on 16/9/6. - */ - -public class PullListLayout extends PtrFrameLayout { - private ListRefreshView listRefreshView = null; - - private boolean isRefresh = true; - - private GestureMoveActionCompat gestureCompat; - - public PullListLayout(Context context) { - this(context, null); - } - - public PullListLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PullListLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initUI(); - } - - private void initUI() { - gestureCompat = new GestureMoveActionCompat(null); - - setRatioOfHeaderHeightToRefresh(1f); - setResistance(3f); - setDurationToCloseHeader(500); - - listRefreshView = new ListRefreshView(getContext()); - setHeaderView(listRefreshView); - addPtrUIHandler(listRefreshView); - - ILoadMoreViewFactory loadMoreViewFactory = new DefaultLoadMoreViewFooter(); - setFooterView(loadMoreViewFactory); - } - - public void setStatusHintColor(int colorResId) { - listRefreshView.setStatusHintColor(colorResId); - } - - public void setUpdateTimeColor(int colorResId) { - listRefreshView.setUpdateTimeColor(colorResId); - } - - public void setPullRefresh(boolean isRefresh) { - this.isRefresh = isRefresh; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent e) { - // 解决 PullListLayout 垂直滑动(下拉刷新)与横向滑动冲突 - if (gestureCompat.onTouchEvent(e, e.getRawX(), e.getRawY())) { - return dispatchTouchEventSupper(e); - } - // 解决双指缩放的冲突 - if (MotionEventCompat.getActionMasked(e) == MotionEvent.ACTION_POINTER_DOWN) { - isRefresh = false; - } - - if (isRefresh) { - return super.dispatchTouchEvent(e); - } - - switch (e.getAction()) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - isRefresh = true; - return super.dispatchTouchEvent(e); - } - - return dispatchTouchEventSupper(e); - } -} \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_enter.xml b/app/src/main/res/anim/dialog_enter.xml deleted file mode 100644 index 530f5786c790033eae5ddbe2afe80aea21c0458e..0000000000000000000000000000000000000000 --- a/app/src/main/res/anim/dialog_enter.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_exit.xml b/app/src/main/res/anim/dialog_exit.xml deleted file mode 100644 index 07c47241662fd7b1cb599d6b9cbce4f2c25df8dd..0000000000000000000000000000000000000000 --- a/app/src/main/res/anim/dialog_exit.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/animator/loading_rotation.xml b/app/src/main/res/animator/loading_rotation.xml deleted file mode 100644 index 610f1a3cc71f32ba7975f4c13e3be6e1cf897416..0000000000000000000000000000000000000000 --- a/app/src/main/res/animator/loading_rotation.xml +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/btn_tab.xml b/app/src/main/res/drawable-v21/btn_tab.xml deleted file mode 100644 index 2561bbcf7dd7afe364a01f216729bfcfffa6b7d4..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable-v21/btn_tab.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_ad_image_mark.xml b/app/src/main/res/drawable/bg_ad_image_mark.xml deleted file mode 100644 index 7fa9530994e8440101a112d1c54d0875c120d598..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/bg_ad_image_mark.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_tab.xml b/app/src/main/res/drawable/bg_tab.xml deleted file mode 100644 index 1d6178b573b65c1d4f998395040fa1a3a92b02ac..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/bg_tab.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_tab.xml b/app/src/main/res/drawable/btn_tab.xml deleted file mode 100644 index 47ccfaaa27de8199cef1ce6b9d31b5d4406d04c3..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/btn_tab.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_default.xml b/app/src/main/res/drawable/ic_default.xml deleted file mode 100644 index 7effebf0d99bcfa2c14a1dcc830797db1486be39..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/ic_default.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/text_tab.xml b/app/src/main/res/drawable/text_tab.xml deleted file mode 100644 index f4650d3c350432625cb334a80b01bdf70b507fbb..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/text_tab.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_drawable_loading.xml b/app/src/main/res/drawable/vector_drawable_loading.xml deleted file mode 100644 index 02528d3c612ad639f70eaf2eabe5a35e7d708fa6..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/vector_drawable_loading.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_drawable_loading2.xml b/app/src/main/res/drawable/vector_drawable_loading2.xml deleted file mode 100644 index a234cde5c4f11bbefb3be97a4fda14161757f2c1..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/vector_drawable_loading2.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_drawable_loading2_anim.xml b/app/src/main/res/drawable/vector_drawable_loading2_anim.xml deleted file mode 100644 index 3abd4760fba86dad492539b3e19560a4a3a9dd75..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/vector_drawable_loading2_anim.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_drawable_loading_anim.xml b/app/src/main/res/drawable/vector_drawable_loading_anim.xml deleted file mode 100644 index ae053e5be2b32a3f2017be129cbb7e27707c748d..0000000000000000000000000000000000000000 --- a/app/src/main/res/drawable/vector_drawable_loading_anim.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_left_and_right_refresh.xml b/app/src/main/res/layout/activity_left_and_right_refresh.xml deleted file mode 100644 index 4cffb5124138b69f699e209fcdacad2c5462860f..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_left_and_right_refresh.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 4e3161c061bc4c056efb3d1b65ac508a69bf63af..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_simple_timeline.xml b/app/src/main/res/layout/activity_simple_timeline.xml deleted file mode 100644 index c44278b7af91ae513f87b0bb058a775a79390f6a..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_simple_timeline.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_with_fragment_and_tablayout_switcher.xml b/app/src/main/res/layout/activity_with_fragment_and_tablayout_switcher.xml deleted file mode 100644 index b1fa1d321f9bc243c9266365a0939016e709743b..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_with_fragment_and_tablayout_switcher.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_with_pull_to_refresh.xml b/app/src/main/res/layout/activity_with_pull_to_refresh.xml deleted file mode 100644 index fe219d7555eb5ea8ea35322b5d5c598dba6e3155..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/activity_with_pull_to_refresh.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_kline.xml b/app/src/main/res/layout/fragment_kline.xml deleted file mode 100644 index b780e50c4d8d707f8bfd9133e89995dd7d514113..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/fragment_kline.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_text.xml b/app/src/main/res/layout/item_text.xml deleted file mode 100644 index 4131c830e80b6af61e6c9893865bbe6f2fe4fac7..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/item_text.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_list_refresh.xml b/app/src/main/res/layout/view_list_refresh.xml deleted file mode 100644 index 9bcb8eee1aa00ba7a6e1710d440b6cda3ec37712..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/view_list_refresh.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cde69bcccec65160d92116f20ffce4fce0b5245c..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 9a078e3e1a42d474c78470a73c7987cf7ac5d9a0..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c133a0cbd379f5af6dbf1a899a0293ca5eccfad0..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index efc028a636dd690a51db5a525cf781a5a7daba68..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfa42f0e7b91d006d22352c9ff2f134e504e3c1d..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 3af2608a4492ef9ae63a77ec3305aedda89594cb..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/image.png b/app/src/main/res/mipmap-xhdpi/image.png deleted file mode 100644 index c0de9c5e4e15b53fbd1ce91a62d5afc9bf5de122..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xhdpi/image.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 324e72cdd7480cb983fa1bcc7ce686e51ef87fe7..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 9bec2e623103ac9713b00cad8502a057c1efda61..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index aee44e138434630332d88b1680f33c4b24c70ab3..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 34947cd6bbf9c729be83edc96ad08a1d42b82bc9..0000000000000000000000000000000000000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/values-v19/styles.xml b/app/src/main/res/values-v19/styles.xml deleted file mode 100644 index 36d26d5215088d03b46ea7f18abadc50dcd8c9fe..0000000000000000000000000000000000000000 --- a/app/src/main/res/values-v19/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 1670889738335f4ad0780a8a9a4fe0a01dd9fc3e..0000000000000000000000000000000000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/attri.xml b/app/src/main/res/values/attri.xml deleted file mode 100644 index af1dabdebfcd653a4b701f4c70999f9317245915..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/attri.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index 4c1a7976cfc9f97578d6f9ceb091d24b4144e33f..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - #1e82d2 - #FF4081 - - - #1e82d2 - - #D8D8D8 - - #dddddd - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml deleted file mode 100644 index cdf4aeab28077c6557504b06e81805cf9fad3b08..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - 44dp - - - 17dp - - - 15dp - - - 15dp - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index d9200f728ebc6c404d5f86fc31ee7fb2dc60604f..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ikvStockChart - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml deleted file mode 100644 index 0398eb54d17f57dd838f53ddf82d60c5649f4754..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/values/warning.xml b/app/src/main/res/values/warning.xml deleted file mode 100644 index 6fca90fe74f7ed6793aba2eb26b1f13258dac158..0000000000000000000000000000000000000000 --- a/app/src/main/res/values/warning.xml +++ /dev/null @@ -1,5 +0,0 @@ - - 程序发生了错误 - 当前网络不给力,请检查您的网络设置 - 当前网络不给力,请稍后重试 - \ No newline at end of file diff --git a/app/src/test/java/com/wordplat/quickstart/ExampleUnitTest.java b/app/src/test/java/com/wordplat/quickstart/ExampleUnitTest.java deleted file mode 100644 index 3514ac630810016c4d5c436a8863a22a2b20f978..0000000000000000000000000000000000000000 --- a/app/src/test/java/com/wordplat/quickstart/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.wordplat.quickstart; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index ca197d0d6b893b9dbfa0cf57b9b6375ff8344bbe..e463f147c46e9b38e8fe2fb8e3f3059b899a057d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,37 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: 'com.huawei.ohos.app' + +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } +} buildscript { repositories { + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' + } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.huawei.ohos:hap:2.4.2.7' + classpath 'com.huawei.ohos:decctest:1.0.0.6' } } allprojects { repositories { - jcenter() - maven { url "https://jitpack.io" } - mavenCentral() - } - tasks.withType(Javadoc) { - options { - encoding "UTF-8" - charSet 'UTF-8' - links "http://docs.oracle.com/javase/7/docs/api" + maven { + url 'https://repo.huaweicloud.com/repository/maven/' + } + maven { + url 'https://developer.huawei.com/repo/' } + jcenter() } } - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/app/.gitignore b/entry/.gitignore similarity index 100% rename from app/.gitignore rename to entry/.gitignore diff --git a/entry/build.gradle b/entry/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..a8ad05e009083713feca18a09c872609c0ba29a1 --- /dev/null +++ b/entry/build.gradle @@ -0,0 +1,30 @@ +apply plugin: 'com.huawei.ohos.hap' +apply plugin: 'com.huawei.ohos.decctest' +ohos { + + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) + testImplementation 'junit:junit:4.13' + ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.100' + implementation 'io.reactivex:rxjava:1.1.9' + implementation 'com.alibaba:fastjson:1.2.55' + implementation project(path: ":ikvstockchart") +} +decc { + supportType = ['html', 'xml'] +} diff --git a/entry/proguard-rules.pro b/entry/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f7666e47561d514b2a76d5a7dfbb43ede86da92a --- /dev/null +++ b/entry/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/entry/src/main/config.json b/entry/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..3d7ffe9b216fa8f7586090333ecc4a995419ed3f --- /dev/null +++ b/entry/src/main/config.json @@ -0,0 +1,164 @@ +{ + "app": { + "bundleName": "com.wordplat.quickstart", + "vendor": "wordplat", + "version": { + "code": 1, + "name": "1.0" + }, + "apiVersion": { + "compatible": 5, + "target": 5 + } + }, + "deviceConfig": {}, + "module": { + "package": "com.wordplat.quickstart", + "name": "com.wordplat.quickstart.base.MyApplication", + "deviceType": [ + "phone" + ], + "reqPermissions": [ + { + "name": "ohos.permission.INTERNET" + }, + { + "name": "ohos.permission.WRITE_MEDIA" + }, + { + "name": "ohos.permission.GET_WIFI_INFO" + }, + { + "name": "ohos.permission.GET_NETWORK_INFO" + } + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry", + "moduleType": "entry" + }, + "abilities": [ + { + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:app_name", + "type": "page", + "launchType": "singleton" + }, + { + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.Enable_Left_And_Right_Refresh_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + }, + { + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.Disable_Left_And_Right_Refresh_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + }, + { + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.MACD_RSI_KDJ_Show_Together_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + },{ + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.Simple_TimeLine_Example_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + },{ + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.With_Pull_To_Refresh_Example_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + },{ + "metaData": { + "customizeData": [ + { + "name": "hwc-theme", + "value": "androidhwext:style/Theme.Emui.NoTitleBar" + } + ] + }, + "orientation": "unspecified", + "name": "com.wordplat.quickstart.ability.With_Fragment_And_TabLayout_Switcher_Example_Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + } + + ] + } +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/Disable_Left_And_Right_Refresh_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/Disable_Left_And_Right_Refresh_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..9cbacf1ec8137eb9e6d4c220e38428489c001272 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/Disable_Left_And_Right_Refresh_Ability.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.slice.Disable_Left_And_Right_Refresh_Slice; + +import ohos.aafwk.content.Intent; + +/** + * Disable_Left_And_Right_Refresh_Ability + * + * @since 2021-04-22 + */ +public class Disable_Left_And_Right_Refresh_Ability extends Enable_Left_And_Right_Refresh_Ability { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(Disable_Left_And_Right_Refresh_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/Enable_Left_And_Right_Refresh_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/Enable_Left_And_Right_Refresh_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..2d48fe86a9a725106dc19f3e9097ab300883cff3 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/Enable_Left_And_Right_Refresh_Ability.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.Enable_Left_And_Right_Refresh_Slice; + +import ohos.aafwk.content.Intent; + +/** + * Enable_Left_And_Right_Refresh_Ability + * + * @since 2021-04-22 + */ +public class Enable_Left_And_Right_Refresh_Ability extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(Enable_Left_And_Right_Refresh_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/MACD_RSI_KDJ_Show_Together_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/MACD_RSI_KDJ_Show_Together_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..793dd8199f523e384272d6e1f054347a2e1b9d43 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/MACD_RSI_KDJ_Show_Together_Ability.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.MACD_RSI_KDJ_Show_Together_Slice; + +import ohos.aafwk.content.Intent; + +/** + * MACD_RSI_KDJ_Show_Together_Ability + * + * @since 2021-04-22 + */ +public class MACD_RSI_KDJ_Show_Together_Ability extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(MACD_RSI_KDJ_Show_Together_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/MainAbility.java b/entry/src/main/java/com/wordplat/quickstart/ability/MainAbility.java new file mode 100644 index 0000000000000000000000000000000000000000..0b2b9f048c5391c81e78058b5229fb8d8c7020bf --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/MainAbility.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.MainSlice; + +import ohos.aafwk.content.Intent; + +/** + * MainAbility + * + * @since 2021-04-22 + */ +public class MainAbility extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(MainSlice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/Simple_TimeLine_Example_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/Simple_TimeLine_Example_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..c833cbadb07d2045b3c2540d599eb5ef370e37d6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/Simple_TimeLine_Example_Ability.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.Simple_TimeLine_Example_Slice; + +import ohos.aafwk.content.Intent; + +/** + * Disable_Left_And_Right_Refresh_Ability + * + * @since 2021-04-22 + */ +public class Simple_TimeLine_Example_Ability extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(Simple_TimeLine_Example_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/With_Fragment_And_TabLayout_Switcher_Example_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/With_Fragment_And_TabLayout_Switcher_Example_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..c32aa47071a44f9c3d11918aa474b58ca92448fb --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/With_Fragment_And_TabLayout_Switcher_Example_Ability.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.With_Fragment_And_TabLayout_Switcher_Example_Slice; + +import ohos.aafwk.content.Intent; + +/** + * MACD_RSI_KDJ_Show_Together_Ability + * + * @since 2021-04-22 + */ +public class With_Fragment_And_TabLayout_Switcher_Example_Ability extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(With_Fragment_And_TabLayout_Switcher_Example_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/With_Pull_To_Refresh_Example_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/With_Pull_To_Refresh_Example_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..4ce35ff943fd292a2ed73104ae15a53918b2c816 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/With_Pull_To_Refresh_Example_Ability.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.slice.With_Pull_To_Refresh_Example_Slice; + +import ohos.aafwk.content.Intent; + +/** + * With_Pull_To_Refresh_Example_Ability + * + * @since 2021-04-30 + */ +public class With_Pull_To_Refresh_Example_Ability extends Enable_Left_And_Right_Refresh_Ability { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(With_Pull_To_Refresh_Example_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/ability/With_RecyclerView_Example_Ability.java b/entry/src/main/java/com/wordplat/quickstart/ability/With_RecyclerView_Example_Ability.java new file mode 100644 index 0000000000000000000000000000000000000000..bd4a18337f5d0d37a1457d1c3a3c4e62928d52e1 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/ability/With_RecyclerView_Example_Ability.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.ability; + +import com.wordplat.quickstart.base.BaseAbility; +import com.wordplat.quickstart.slice.With_Pull_To_Refresh_Example_Slice; + +import ohos.aafwk.content.Intent; + +/** + * With_RecyclerView_Example_Ability + * + * @since 2021-04-30 + */ +public class With_RecyclerView_Example_Ability extends BaseAbility { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(With_Pull_To_Refresh_Example_Slice.class.getName()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java b/entry/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..01ebca79a9f7c1a647ccb1293273e6a5bc28cdcb --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/adapter/TextAdapter.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.adapter; + +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.ability.Disable_Left_And_Right_Refresh_Ability; +import com.wordplat.quickstart.ability.Enable_Left_And_Right_Refresh_Ability; +import com.wordplat.quickstart.ability.MACD_RSI_KDJ_Show_Together_Ability; +import com.wordplat.quickstart.ability.Simple_TimeLine_Example_Ability; +import com.wordplat.quickstart.ability.With_Fragment_And_TabLayout_Switcher_Example_Ability; +import com.wordplat.quickstart.ability.With_Pull_To_Refresh_Example_Ability; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.aafwk.content.Operation; +import ohos.agp.components.BaseItemProvider; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.Text; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by afon on 2017/2/10. + * + * @since 2021-05-08 + */ +public class TextAdapter extends BaseItemProvider { + private static List textList = new ArrayList<>(); + private static final int NUM_0 = 0; + private static final int NUM_1 = 1; + private static final int NUM_2 = 2; + private static final int NUM_3 = 3; + private static final int NUM_4 = 4; + private static final int NUM_5 = 5; + + private final LayoutScatter inflater; + + + static { + textList.add("左滑右滑加载(Enable left and right refresh)"); + textList.add("禁用左滑右滑加载"); + textList.add("多个指标共同显示、联动(MACD, RSI, KDJ)"); + textList.add("在Fragment中使用"); + textList.add("带有下拉刷新的需求中使用"); + /** + * textList.add("动态改变颜色、尺寸配置"); // 还没有准备好 + * textList.add("在 RecyclerView 列表中使用"); // 还没有准备好 + */ + textList.add("简单分时图"); + } + + private AbilitySlice mAbilitySlice; + + /** + * TextAdapter + * + * @param slice + */ + public TextAdapter(AbilitySlice slice) { + mAbilitySlice = slice; + inflater = LayoutScatter.getInstance(slice); + } + + @Override + public int getCount() { + return textList.size(); + } + + @Override + public Object getItem(int i) { + return textList.get(i); + } + + /** + * getItemId + * + * @param itemId + * @return long number + */ + @Override + public long getItemId(int itemId) { + return itemId; + } + + @Override + public Component getComponent(int i, Component component, ComponentContainer componentContainer) { + component = inflater.parse(ResourceTable.Layout_item_text, componentContainer, false); + Component view = component.findComponentById(ResourceTable.Id_view); + Text text = (Text) component.findComponentById(ResourceTable.Id_text); + text.setText(textList.get(i)); + if (i == getCount() - 1) { + view.setVisibility(Component.HIDE); + } + text.setClickedListener(new Component.ClickedListener() { + @Override + public void onClick(Component component) { + switch (i) { + case NUM_0: + nextPage(Enable_Left_And_Right_Refresh_Ability.class); + break; + + case NUM_1: + nextPage(Disable_Left_And_Right_Refresh_Ability.class); + break; + + case NUM_2: + nextPage(MACD_RSI_KDJ_Show_Together_Ability.class); + break; + + case NUM_3: + nextPage(With_Fragment_And_TabLayout_Switcher_Example_Ability.class); + break; + case NUM_4: + nextPage(With_Pull_To_Refresh_Example_Ability.class); + break; + case NUM_5: + nextPage(Simple_TimeLine_Example_Ability.class); + break; + default: + break; + } + } + }); + return component; + } + + /** + * nextPage + * + * @param pageName + */ + public void nextPage(Class pageName) { + Intent secondIntent = new Intent(); + Operation operation = new Intent.OperationBuilder() + .withDeviceId("") + .withBundleName(mAbilitySlice.getBundleName()) + .withAbilityName(pageName.getName()) + .build(); + secondIntent.setOperation(operation); + mAbilitySlice.startAbility(secondIntent); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/app/AppRuntime.java b/entry/src/main/java/com/wordplat/quickstart/app/AppRuntime.java new file mode 100644 index 0000000000000000000000000000000000000000..354f71269fb1fc9cdd510069e81ef0c3cc612d45 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/app/AppRuntime.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.app; + +import ohos.aafwk.ability.Ability; +import ohos.app.Context; + +import java.lang.ref.WeakReference; +import java.util.LinkedList; + +/** + * Created by afon on 2017/1/24. + */ +public class AppRuntime { + + /** + * 静态类型的 Context,你懂的 + */ + public static Context sContext; + /** + * 查找当前的 Activity 栈用的 + */ + public static LinkedList sActivities = new LinkedList<>(); + /** + * 快速查找位于栈顶的 Ability 用的 + */ + public static WeakReference sActivityStops = new WeakReference<>(null); + /** + * 与服务器通信接口版本号。本值会自动读取到 gradle.properties 文件中定义的,不需要手动修改 + */ + public static String serverVersion = "1.0"; + /** + * 本APP的版本号。本值会自动读取到文件中定义的,不需要手动修改 + */ + public static String appVersion = "1.0.0"; +} diff --git a/app/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java b/entry/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java similarity index 45% rename from app/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java rename to entry/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java index f26ed2ddacf481007b6007c3a615864be138bd64..b2bd23c5697b2d81cb65f2196c49c0765f7698d8 100644 --- a/app/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java +++ b/entry/src/main/java/com/wordplat/quickstart/app/DeviceRuntime.java @@ -3,10 +3,22 @@ package com.wordplat.quickstart.app; /** * Created by afon on 2017/2/4. */ - public class DeviceRuntime { - + /** + * IMEI + */ public static String IMEI = ""; - + /** + * DEVICE_ID + */ public static String DEVICE_ID = ""; + + /** + * PULL_START + */ + public static String PULL_START = "PULL_START"; + /** + * PULL_END + */ + public static String PULL_END = "PULL_END"; } diff --git a/entry/src/main/java/com/wordplat/quickstart/base/BaseAbility.java b/entry/src/main/java/com/wordplat/quickstart/base/BaseAbility.java new file mode 100644 index 0000000000000000000000000000000000000000..845656cef74e05b5b4c7deef1ab963c089ebc243 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/base/BaseAbility.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.base; + +import com.wordplat.quickstart.xutils.x; + +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; +import ohos.agp.render.render3d.BuildConfig; + +/** + * author :Administrator + * date : 2021/4/28 9:55 + * package:BaseAbilitySlice + * description : + * + * @since 2021-04-22 + */ +public class BaseAbility extends Ability { + + @Override + protected void onStart(Intent intent) { + super.onStart(intent); + x.Ext.init(this); + x.Ext.setDebug(BuildConfig.DEBUG); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/base/BaseAbilitySlice.java b/entry/src/main/java/com/wordplat/quickstart/base/BaseAbilitySlice.java new file mode 100644 index 0000000000000000000000000000000000000000..75ce7fe2d2fcc016977be2d4b5f115358755de4c --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/base/BaseAbilitySlice.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.base; + +import com.wordplat.quickstart.ResourceTable; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.agp.utils.Color; +import ohos.agp.window.service.WindowManager; + +/** + * author :Administrator + * date : 2021/4/28 9:55 + * package:BaseAbilitySlice + * description : + * + * @since 2021-04-22 + */ +public class BaseAbilitySlice extends AbilitySlice { + @Override + protected void onStart(Intent intent) { + super.onStart(intent); + setStatusBarColor(ResourceTable.Color_colorPrimaryDark); + } + + /** + * setStatusBarColor + * + * @param colorResId + */ + protected void setStatusBarColor(int colorResId) { + WindowManager.getInstance().getTopWindow().get().setStatusBarColor(Color.rgb(30,130,210)); + + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/base/MyApplication.java b/entry/src/main/java/com/wordplat/quickstart/base/MyApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..00916b1934bf993c2b4524aaa14122977003f833 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/base/MyApplication.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.base; + +import com.wordplat.quickstart.app.AppRuntime; + +import ohos.aafwk.ability.AbilityPackage; + +/** + * MyApplication + * + * @since 2021-04-22 + */ +public class MyApplication extends AbilityPackage { + @Override + public void onInitialize() { + super.onInitialize(); + AppRuntime.sContext = this; + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/bean/BtcBean.java b/entry/src/main/java/com/wordplat/quickstart/bean/BtcBean.java similarity index 97% rename from app/src/main/java/com/wordplat/quickstart/bean/BtcBean.java rename to entry/src/main/java/com/wordplat/quickstart/bean/BtcBean.java index 71bba5bfe67bab36bbc542a7fa831dee02b0b8f5..05d5919689629331b7b1e2634e3724e2141c8f27 100644 --- a/app/src/main/java/com/wordplat/quickstart/bean/BtcBean.java +++ b/entry/src/main/java/com/wordplat/quickstart/bean/BtcBean.java @@ -5,10 +5,9 @@ package com.wordplat.quickstart.bean; *

Date: 2017/4/16

* * @author afon + * @since 2017-04-16 */ - public class BtcBean { - private long date; private float price; diff --git a/app/src/main/java/com/wordplat/quickstart/bean/KLineBean.java b/entry/src/main/java/com/wordplat/quickstart/bean/KLineBean.java similarity index 98% rename from app/src/main/java/com/wordplat/quickstart/bean/KLineBean.java rename to entry/src/main/java/com/wordplat/quickstart/bean/KLineBean.java index 3bd7b0d379235fdb4d1014bd363a808bab5c3511..cd8596cb4917cf205a11a5a0c1334781cefc8023 100644 --- a/app/src/main/java/com/wordplat/quickstart/bean/KLineBean.java +++ b/entry/src/main/java/com/wordplat/quickstart/bean/KLineBean.java @@ -5,10 +5,9 @@ package com.wordplat.quickstart.bean; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class KLineBean { - private float open; private float high; diff --git a/app/src/main/java/com/wordplat/quickstart/bean/ResultBean.java b/entry/src/main/java/com/wordplat/quickstart/bean/ResultBean.java similarity index 96% rename from app/src/main/java/com/wordplat/quickstart/bean/ResultBean.java rename to entry/src/main/java/com/wordplat/quickstart/bean/ResultBean.java index f4d0a2e3c23467f85ec4afa3b69dd809a44452ab..7699927b8c4d6134819244c642301d9b904f76aa 100644 --- a/app/src/main/java/com/wordplat/quickstart/bean/ResultBean.java +++ b/entry/src/main/java/com/wordplat/quickstart/bean/ResultBean.java @@ -5,10 +5,9 @@ package com.wordplat.quickstart.bean; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class ResultBean { - private int resultCode; private String resultMessage; diff --git a/app/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java b/entry/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java similarity index 79% rename from app/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java rename to entry/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java index c5245b2dbb9ea3dcae2100635651cfaf21a4d0b4..52ff561a0e332e2513f59c85e4b14eab19280d3c 100644 --- a/app/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java +++ b/entry/src/main/java/com/wordplat/quickstart/bean/response/IResultResponse.java @@ -5,17 +5,20 @@ package com.wordplat.quickstart.bean.response; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public interface IResultResponse { - /** * 服务器返回的逻辑结果响应码 + * + * @return 响应码 */ int getResultCode(); /** * 服务器返回的逻辑结果错误信息 + * + * @return 错误信息 */ String getResultDescr(); } diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSON.java b/entry/src/main/java/com/wordplat/quickstart/json/JSON.java new file mode 100644 index 0000000000000000000000000000000000000000..64e39f94d1f62b54a85a0aca9e6a69aeef74e86f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSON.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +/** + * JSON + * + * @since 2021-05-08 + */ +class JSON { + private JSON() { + } + + static double checkDouble(double douNum) throws JSONException { + if (Double.isInfinite(douNum) || Double.isNaN(douNum)) { + throw new JSONException("Forbidden numeric value: " + douNum); + } + return douNum; + } + + static Boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } else if (value instanceof String) { + String stringValue = (String) value; + if ("true".equalsIgnoreCase(stringValue)) { + return true; + } else if ("false".equalsIgnoreCase(stringValue)) { + return false; + } + } + return null; + } + + static Double toDouble(Object value) { + if (value instanceof Double) { + return (Double) value; + } else if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof String) { + try { + return Double.valueOf((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static Integer toInteger(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof String) { + try { + return (int) Double.parseDouble((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static Long toLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else if (value instanceof String) { + try { + return (long) Double.parseDouble((String) value); + } catch (NumberFormatException ignored) { + } + } + return null; + } + + static String toString(Object value) { + if (value instanceof String) { + return (String) value; + } else if (value != null) { + return String.valueOf(value); + } + return null; + } + + public static JSONException typeMismatch(Object indexOrName, Object actual, + String requiredType) throws JSONException { + if (actual == null) { + throw new JSONException("Value at " + indexOrName + " is null."); + } else { + throw new JSONException("Value " + actual + " at " + indexOrName + + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + } + + public static JSONException typeMismatch(Object actual, String requiredType) + throws JSONException { + if (actual == null) { + throw new JSONException("Value is null."); + } else { + throw new JSONException("Value " + actual + + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSONArray.java b/entry/src/main/java/com/wordplat/quickstart/json/JSONArray.java new file mode 100644 index 0000000000000000000000000000000000000000..c1f6a360514813d11ffcaaf3f39017afcdb666d5 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSONArray.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * A dense indexed sequence of values. Values may be any mix of + * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings, + * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}. + * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() + * infinities}, or of any type not listed here. + * + *

{@code JSONArray} has the same type coercion behavior and + * optional/mandatory accessors as {@link JSONObject}. See that class' + * documentation for details. + * + *

Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value {@link + * JSONObject#NULL}. In particular, {@code get} fails if the requested index + * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}. + * + *

Instances of this class are not thread safe. Although this class is + * nonfinal, it was not designed for inheritance and should not be subclassed. + * In particular, self-use by overridable methods is not specified. See + * Effective Java Item 17, "Design and Document or inheritance or else + * prohibit it" for further information. + */ +public class JSONArray { + private final List values; + + /** + * Creates a {@code JSONArray} with no values + */ + public JSONArray() { + values = new ArrayList(); + } + + /** + * Accept a raw type for API compatibility + * + * @param copyFrom + */ + public JSONArray(Collection copyFrom) { + this(); + if (copyFrom != null) { + for (Iterator it = copyFrom.iterator(); it.hasNext(); ) { + put(JSONObject.wrap(it.next())); + } + } + } + + /** + * JSONArray + * + * @param readFrom + * @throws JSONException + */ + public JSONArray(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just + * parse to temporary JSONArray and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONArray) { + values = ((JSONArray) object).values; + } else { + throw JSON.typeMismatch(object, "JSONArray"); + } + } + + /** + * JSONArray + * + * @param json + * @throws JSONException + */ + public JSONArray(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * JSONArray + * + * @param array + * @throws JSONException + */ + public JSONArray(Object array) throws JSONException { + if (!array.getClass().isArray()) { + throw new JSONException("Not a primitive array: " + array.getClass()); + } + final int length = Array.getLength(array); + values = new ArrayList(length); + for (int i = 0; i < length; ++i) { + put(JSONObject.wrap(Array.get(array, i))); + } + } + + /** + * length + * + * @return length + */ + public int length() { + return values.size(); + } + + /** + * JSONArray + * + * @param value + * @return JSONArray + */ + public JSONArray put(boolean value) { + values.add(value); + return this; + } + + /** + * JSONArray + * + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(double value) throws JSONException { + values.add(JSON.checkDouble(value)); + return this; + } + + /** + * JSONArray + * + * @param value + * @return JSONArray + */ + public JSONArray put(int value) { + values.add(value); + return this; + } + + /** + * JSONArray + * + * @param value + * @return JSONArray + */ + public JSONArray put(long value) { + values.add(value); + return this; + } + + /** + * JSONArray + * + * @param value + * @return JSONArray + */ + public JSONArray put(Object value) { + values.add(value); + return this; + } + + void checkedPut(Object value) throws JSONException { + if (value instanceof Number) { + JSON.checkDouble(((Number) value).doubleValue()); + } + + put(value); + } + + /** + * JSONArray + * + * @param index + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(int index, boolean value) throws JSONException { + return put(index, (Boolean) value); + } + + /** + * JSONArray + * + * @param index + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(int index, double value) throws JSONException { + return put(index, (Double) value); + } + + /** + * JSONArray + * + * @param index + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(int index, int value) throws JSONException { + return put(index, (Integer) value); + } + + /** + * put + * + * @param index + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(int index, long value) throws JSONException { + return put(index, (Long) value); + } + + /** + * put + * + * @param index + * @param value + * @return JSONArray + * @throws JSONException + */ + public JSONArray put(int index, Object value) throws JSONException { + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + while (values.size() <= index) { + values.add(null); + } + values.set(index, value); + return this; + } + + /** + * isNull + * + * @param index + * @return boolean + */ + public boolean isNull(int index) { + Object value = opt(index); + return value == null || value == JSONObject.NULL; + } + + /** + * get + * + * @param index + * @return Object + * @throws JSONException + */ + public Object get(int index) throws JSONException { + try { + Object value = values.get(index); + if (value == null) { + throw new JSONException("Value at " + index + " is null."); + } + return value; + } catch (IndexOutOfBoundsException e) { + throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")"); + } + } + + /** + * opt + * + * @param index + * @return Object + */ + public Object opt(int index) { + if (index < 0 || index >= values.size()) { + return null; + } + return values.get(index); + } + + /** + * remove + * + * @param index + * @return Object + */ + public Object remove(int index) { + if (index < 0 || index >= values.size()) { + return null; + } + return values.remove(index); + } + + /** + * getBoolean + * + * @param index + * @return boolean + * @throws JSONException + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "boolean"); + } + return result; + } + + /** + * optBoolean + * + * @param index + * @return boolean + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * optBoolean + * + * @param index + * @param fallback + * @return boolean + */ + public boolean optBoolean(int index, boolean fallback) { + Object object = opt(index); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * getDouble + * + * @param index + * @return double + * @throws JSONException + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "double"); + } + return result; + } + + /** + * optDouble + * + * @param index + * @return double + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * optDouble + * + * @param index + * @param fallback + * @return double + */ + public double optDouble(int index, double fallback) { + Object object = opt(index); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * getInt + * + * @param index + * @return int + * @throws JSONException + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "int"); + } + return result; + } + + /** + * optInt + * + * @param index + * @return int + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * optInt + * + * @param index + * @param fallback + * @return int + */ + public int optInt(int index, int fallback) { + Object object = opt(index); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * getLong + * + * @param index + * @return long + * @throws JSONException + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "long"); + } + return result; + } + + /** + * optLong + * + * @param index + * @return long + */ + public long optLong(int index) { + return optLong(index, 0L); + } + + /** + * optLong + * + * @param index + * @param fallback + * @return long + */ + public long optLong(int index, long fallback) { + Object object = opt(index); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * getString + * + * @param index + * @return String + * @throws JSONException + */ + public String getString(int index) throws JSONException { + Object object = get(index); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "String"); + } + return result; + } + + /** + * optString + * + * @param index + * @return String + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * optString + * + * @param index + * @param fallback + * @return String + */ + public String optString(int index, String fallback) { + Object object = opt(index); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * getJSONArray + * + * @param index + * @return JSONArray + * @throws JSONException + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } else { + throw JSON.typeMismatch(index, object, "JSONArray"); + } + } + + /** + * optJSONArray + * + * @param index + * @return JSONArray + */ + public JSONArray optJSONArray(int index) { + Object object = opt(index); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * getJSONObject + * + * @param index + * @return JSONObject + * @throws JSONException + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } else { + throw JSON.typeMismatch(index, object, "JSONObject"); + } + } + + /** + * optJSONObject + * + * @param index + * @return JSONObject + */ + public JSONObject optJSONObject(int index) { + Object object = opt(index); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * toJSONObject + * + * @param names + * @return JSONObject + * @throws JSONException + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + JSONObject result = new JSONObject(); + int length = Math.min(names.length(), values.size()); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(name, opt(i)); + } + return result; + } + + /** + * join + * + * @param separator + * @return String + * @throws JSONException + */ + public String join(String separator) throws JSONException { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + for (int i = 0, size = values.size(); i < size; i++) { + if (i > 0) { + stringer.out.append(separator); + } + stringer.value(values.get(i)); + } + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.out.toString(); + } + + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } catch (JSONException e) { + return null; + } + } + + /** + * toString + * + * @param indentSpaces + * @return String + * @throws JSONException + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.array(); + for (Object value : values) { + stringer.value(value); + } + stringer.endArray(); + } + + @Override + public boolean equals(Object o) { + return o instanceof JSONArray && ((JSONArray) o).values.equals(values); + } + + @Override + public int hashCode() { + // diverge from the original, which doesn't implement hashCode + return values.hashCode(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSONException.java b/entry/src/main/java/com/wordplat/quickstart/json/JSONException.java new file mode 100644 index 0000000000000000000000000000000000000000..ebf421205ba65c561721674154ff1ea1c29765cb --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSONException.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Thrown to indicate a problem with the JSON API. Such problems include: + *
    + *
  • Attempts to parse or construct malformed documents + *
  • Use of null as a name + *
  • Use of numeric types not available to JSON, such as {@link + * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + *
  • Lookups using an out of range index or nonexistent name + *
  • Type mismatches on lookups + *
+ * + *

Although this is a checked exception, it is rarely recoverable. Most + * callers should simply wrap this exception in an unchecked exception and + * rethrow: + *

  public JSONArray toJSONObject() {
+ *     try {
+ *         JSONObject result = new JSONObject();
+ *         ...
+ *     } catch (JSONException e) {
+ *         throw new RuntimeException(e);
+ *     }
+ * }
+ */ +public class JSONException extends Exception { + /** + * JSONException + * + * @param s + */ + public JSONException(String s) { + super(s); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSONObject.java b/entry/src/main/java/com/wordplat/quickstart/json/JSONObject.java new file mode 100644 index 0000000000000000000000000000000000000000..29040e3a56f315777eed2d144c57800fffb274fc --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSONObject.java @@ -0,0 +1,824 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +import java.util.*; + +/** + * A modifiable set of name/value mappings. Names are unique, non-null strings. + * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray + * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}. + * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link + * Double#isInfinite() infinities}, or of any type not listed here. + * + *

This class can coerce values to another type when requested. + *

+ * + *

This class can look up both mandatory and optional values: + *

    + *
  • Use getType() to retrieve a mandatory value. This + * fails with a {@code JSONException} if the requested name has no value + * or if the value cannot be coerced to the requested type. + *
  • Use optType() to retrieve an optional value. This + * returns a system- or user-supplied default if the requested name has no + * value or if the value cannot be coerced to the requested type. + *
+ * + *

Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value {@link + * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the + * named entry from the object but {@code put(name, JSONObject.NULL)} stores an + * entry whose value is {@code JSONObject.NULL}. + * + *

Instances of this class are not thread safe. Although this class is + * nonfinal, it was not designed for inheritance and should not be subclassed. + * In particular, self-use by overrideable methods is not specified. See + * Effective Java Item 17, "Design and Document or inheritance or else + * prohibit it" for further information. + */ +public class JSONObject { + private static final Double NEGATIVE_ZERO = -0d; + private final LinkedHashMap nameValuePairs; + + /** + * JSONObject + */ + public JSONObject() { + nameValuePairs = new LinkedHashMap(); + } + + /** + * Object + */ + public static final Object NULL = new Object() { + @Override + public boolean equals(Object o) { + return o == this || o == null; // API specifies this broken equals implementation + } + + @Override + public String toString() { + return "null"; + } + }; + + /** + * accept a raw type for API compatibility + * + * @param copyFrom + * @throws NullPointerException + */ + public JSONObject(Map copyFrom) { + this(); + Map contentsTyped = (Map) copyFrom; + for (Map.Entry entry : contentsTyped.entrySet()) { + /* + * Deviate from the original by checking that keys are non-null and + * of the proper type. (We still defer validating the values). + */ + String key = (String) entry.getKey(); + if (key == null) { + + throw new NullPointerException("key == null"); + } + nameValuePairs.put(key, wrap(entry.getValue())); + } + } + + /** + * JSONObject + * + * @param readFrom + * @throws JSONException + */ + public JSONObject(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just + * parse to temporary JSONObject and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONObject) { + this.nameValuePairs = ((JSONObject) object).nameValuePairs; + } else { + throw JSON.typeMismatch(object, "JSONObject"); + } + } + + /** + * JSONObject + * + * @param json + * @throws JSONException + */ + public JSONObject(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * JSONObject + * + * @param copyFrom + * @param names + * @throws JSONException + */ + public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { + this(); + for (String name : names) { + Object value = copyFrom.opt(name); + if (value != null) { + nameValuePairs.put(name, value); + } + } + } + + /** + * length + * + * @return int + */ + public int length() { + return nameValuePairs.size(); + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject put(String name, boolean value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject put(String name, double value) throws JSONException { + nameValuePairs.put(checkName(name), JSON.checkDouble(value)); + return this; + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject put(String name, int value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject put(String name, long value) throws JSONException { + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject put(String name, Object value) throws JSONException { + if (value == null) { + nameValuePairs.remove(name); + return this; + } + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * JSONObject + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject putOpt(String name, Object value) throws JSONException { + if (name == null || value == null) { + return this; + } + return put(name, value); + } + + /** + * accumulate + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject accumulate(String name, Object value) throws JSONException { + Object current = nameValuePairs.get(checkName(name)); + if (current == null) { + return put(name, value); + } + + if (current instanceof JSONArray) { + JSONArray array = (JSONArray) current; + array.checkedPut(value); + } else { + JSONArray array = new JSONArray(); + array.checkedPut(current); + array.checkedPut(value); + nameValuePairs.put(name, array); + } + return this; + } + + /** + * append + * + * @param name + * @param value + * @return JSONObject + * @throws JSONException + */ + public JSONObject append(String name, Object value) throws JSONException { + Object current = nameValuePairs.get(checkName(name)); + + final JSONArray array; + if (current instanceof JSONArray) { + array = (JSONArray) current; + } else if (current == null) { + JSONArray newArray = new JSONArray(); + nameValuePairs.put(name, newArray); + array = newArray; + } else { + throw new JSONException("Key " + name + " is not a JSONArray"); + } + + array.checkedPut(value); + + return this; + } + + /** + * checkName + * + * @param name + * @return JSONObject + * @throws JSONException + */ + String checkName(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + return name; + } + + /** + * remove + * + * @param name + * @return Object + */ + public Object remove(String name) { + return nameValuePairs.remove(name); + } + + /** + * isNull + * + * @param name + * @return boolean + */ + public boolean isNull(String name) { + Object value = nameValuePairs.get(name); + return value == null || value == NULL; + } + + /** + * has + * + * @param name + * @return boolean + */ + public boolean has(String name) { + return nameValuePairs.containsKey(name); + } + + /** + * get + * + * @param name + * @return Object + * @throws JSONException + */ + public Object get(String name) throws JSONException { + Object result = nameValuePairs.get(name); + if (result == null) { + throw new JSONException("No value for " + name); + } + return result; + } + + /** + * opt + * + * @param name + * @return Object + */ + public Object opt(String name) { + return nameValuePairs.get(name); + } + + /** + * getBoolean + * + * @param name + * @return boolean + * @throws JSONException + */ + public boolean getBoolean(String name) throws JSONException { + Object object = get(name); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "boolean"); + } + return result; + } + + /** + * optBoolean + * + * @param name + * @return boolean + */ + public boolean optBoolean(String name) { + return optBoolean(name, false); + } + + /** + * optBoolean + * + * @param name + * @param fallback + * @return boolean + */ + public boolean optBoolean(String name, boolean fallback) { + Object object = opt(name); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * getDouble + * + * @param name + * @return double + * @throws JSONException + */ + public double getDouble(String name) throws JSONException { + Object object = get(name); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "double"); + } + return result; + } + + /** + * optDouble + * + * @param name + * @return double + */ + public double optDouble(String name) { + return optDouble(name, Double.NaN); + } + + /** + * optDouble + * + * @param name + * @param fallback + * @return double + */ + public double optDouble(String name, double fallback) { + Object object = opt(name); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * getInt + * + * @param name + * @return int + * @throws JSONException + */ + public int getInt(String name) throws JSONException { + Object object = get(name); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "int"); + } + return result; + } + + /** + * optInt + * + * @param name + * @return int + */ + public int optInt(String name) { + return optInt(name, 0); + } + + /** + * optInt + * + * @param name + * @param fallback + * @return int + */ + public int optInt(String name, int fallback) { + Object object = opt(name); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * getLong + * + * @param name + * @return long + * @throws JSONException + */ + public long getLong(String name) throws JSONException { + Object object = get(name); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "long"); + } + return result; + } + + /** + * optLong + * + * @param name + * @return long + */ + public long optLong(String name) { + return optLong(name, 0L); + } + + /** + * optLong + * + * @param name + * @param fallback + * @return long + */ + public long optLong(String name, long fallback) { + Object object = opt(name); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * getString + * + * @param name + * @return String + * @throws JSONException + */ + public String getString(String name) throws JSONException { + Object object = get(name); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "String"); + } + return result; + } + + /** + * optString + * + * @param name + * @return String + */ + public String optString(String name) { + return optString(name, ""); + } + + /** + * optString + * + * @param name + * @param fallback + * @return String + */ + public String optString(String name, String fallback) { + Object object = opt(name); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * getJSONArray + * + * @param name + * @return JSONArray + * @throws JSONException + */ + public JSONArray getJSONArray(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONArray) { + return (JSONArray) object; + } else { + throw JSON.typeMismatch(name, object, "JSONArray"); + } + } + + /** + * optJSONArray + * + * @param name + * @return JSONArray + */ + public JSONArray optJSONArray(String name) { + Object object = opt(name); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * getJSONObject + * + * @param name + * @return JSONObject + * @throws JSONException + */ + public JSONObject getJSONObject(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONObject) { + return (JSONObject) object; + } else { + throw JSON.typeMismatch(name, object, "JSONObject"); + } + } + + /** + * optJSONObject + * + * @param name + * @return JSONObject + */ + public JSONObject optJSONObject(String name) { + Object object = opt(name); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * toJSONArray + * + * @param names + * @return JSONArray + * @throws JSONException + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + JSONArray result = new JSONArray(); + if (names == null) { + return null; + } + int length = names.length(); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(opt(name)); + } + return result; + } + + /** + * keys + * + * @return Iterator + */ + public Iterator keys() { + return nameValuePairs.keySet().iterator(); + } + + /** + * keySet + * + * @return Set + */ + public Set keySet() { + return nameValuePairs.keySet(); + } + + /** + * names + * + * @return JSONArray + */ + public JSONArray names() { + return nameValuePairs.isEmpty() + ? null + : new JSONArray(new ArrayList(nameValuePairs.keySet())); + } + + + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } catch (JSONException e) { + return null; + } + } + + /** + * toString + * + * @param indentSpaces + * @return String + * @throws JSONException + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + /** + * writeTo + * + * @param stringer + * @throws JSONException + */ + void writeTo(JSONStringer stringer) throws JSONException { + stringer.object(); + for (Map.Entry entry : nameValuePairs.entrySet()) { + stringer.key(entry.getKey()).value(entry.getValue()); + } + stringer.endObject(); + } + + /** + * numberToString + * + * @param number + * @return String + * @throws JSONException + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Number must be non-null"); + } + + double doubleValue = number.doubleValue(); + JSON.checkDouble(doubleValue); + + // the original returns "-0" instead of "-0.0" for negative zero + if (number.equals(NEGATIVE_ZERO)) { + return "-0"; + } + + long longValue = number.longValue(); + if (doubleValue == (double) longValue) { + return Long.toString(longValue); + } + + return number.toString(); + } + + /** + * quote + * + * @param data + * @return String + * @throws AssertionError + */ + public static String quote(String data) { + if (data == null) { + return "\"\""; + } + try { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + stringer.value(data); + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.toString(); + } catch (JSONException e) { + throw new AssertionError(); + } + } + + /** + * wrap + * + * @param o + * @return Object + */ + public static Object wrap(Object o) { + if (o == null) { + return NULL; + } + if (o instanceof JSONArray || o instanceof JSONObject) { + return o; + } + if (o.equals(NULL)) { + return o; + } + try { + if (o instanceof Collection) { + return new JSONArray((Collection) o); + } else if (o.getClass().isArray()) { + return new JSONArray(o); + } + if (o instanceof Map) { + return new JSONObject((Map) o); + } + if (o instanceof Boolean || + o instanceof Byte || + o instanceof Character || + o instanceof Double || + o instanceof Float || + o instanceof Integer || + o instanceof Long || + o instanceof Short || + o instanceof String) { + return o; + } + if (o.getClass().getPackage().getName().startsWith("java.")) { + return o.toString(); + } + } catch (Exception ignored) { + LogUtil.e("error:" + ignored); + } + return null; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSONStringer.java b/entry/src/main/java/com/wordplat/quickstart/json/JSONStringer.java new file mode 100644 index 0000000000000000000000000000000000000000..ab3247c241f5970189deb8de0e73d065617e3f3a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSONStringer.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most + * application developers should use those methods directly and disregard this + * API. For example:

+ * JSONObject object = ...
+ * String json = object.toString();
+ * + *

Stringers only encode well-formed JSON strings. In particular: + *

    + *
  • The stringer must have exactly one top-level array or object. + *
  • Lexical scopes must be balanced: every call to {@link #array} must + * have a matching call to {@link #endArray} and every call to {@link + * #object} must have a matching call to {@link #endObject}. + *
  • Arrays may not contain keys (property names). + *
  • Objects must alternate keys (property names) and values. + *
  • Values are inserted with either literal {@link #value(Object) value} + * calls, or by nesting arrays or objects. + *
+ * Calls that would result in a malformed JSON string will fail with a + * {@link JSONException}. + * + *

This class provides no facility for pretty-printing (ie. indenting) + * output. To encode indented output, use {@link JSONObject#toString(int)} or + * {@link JSONArray#toString(int)}. + * + *

Some implementations of the API support at most 20 levels of nesting. + * Attempts to create more than 20 levels of nesting may fail with a {@link + * JSONException}. + * + *

Each stringer may be used to encode a single top level value. Instances of + * this class are not thread safe. Although this class is nonfinal, it was not + * designed for inheritance and should not be subclassed. In particular, + * self-use by overrideable methods is not specified. See Effective Java + * Item 17, "Design and Document or inheritance or else prohibit it" for further + * information. + */ +public class JSONStringer { + + /** + * The output data, containing at most one top-level array or object. + */ + final StringBuilder out = new StringBuilder(); + + /** + * Lexical scoping elements within this stringer, necessary to insert the + * appropriate separator characters (ie. commas and colons) and to detect + * nesting errors. + */ + enum Scope { + + /** + * An array with no elements requires no separators or newlines before + * it is closed. + */ + EMPTY_ARRAY, + + /** + * A array with at least one value requires a comma and newline before + * the next element. + */ + NONEMPTY_ARRAY, + + /** + * An object with no keys or values requires no separators or newlines + * before it is closed. + */ + EMPTY_OBJECT, + + /** + * An object whose most recent element is a key. The next element must + * be a value. + */ + DANGLING_KEY, + + /** + * An object with at least one name/value pair requires a comma and + * newline before the next element. + */ + NONEMPTY_OBJECT, + + /** + * A special bracketless array needed by JSONStringer.join() and + * JSONObject.quote() only. Not used for JSON encoding. + */ + NULL, + } + + /** + * Unlike the original implementation, this stack isn't limited to 20 + * levels of nesting. + */ + private final List stack = new ArrayList(); + + /** + * A string containing a full set of spaces for a single level of + * indentation, or null for no pretty printing. + */ + private final String indent; + + /** + * JSONStringer + */ + public JSONStringer() { + indent = null; + } + + /** + * JSONStringer + * + * @param indentSpaces + */ + JSONStringer(int indentSpaces) { + char[] indentChars = new char[indentSpaces]; + Arrays.fill(indentChars, ' '); + indent = new String(indentChars); + } + + /** + * array + * + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer array() throws JSONException { + return open(Scope.EMPTY_ARRAY, "["); + } + + /** + * endArray + * + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer endArray() throws JSONException { + return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); + } + + /** + * object + * + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer object() throws JSONException { + return open(Scope.EMPTY_OBJECT, "{"); + } + + /** + * endObject + * + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer endObject() throws JSONException { + return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); + } + + + JSONStringer open(Scope empty, String openBracket) throws JSONException { + if (stack.isEmpty() && out.length() > 0) { + throw new JSONException("Nesting problem: multiple top-level roots"); + } + beforeValue(); + stack.add(empty); + out.append(openBracket); + return this; + } + + + JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { + Scope context = peek(); + if (context != nonempty && context != empty) { + throw new JSONException("Nesting problem"); + } + + stack.remove(stack.size() - 1); + if (context == nonempty) { + newline(); + } + out.append(closeBracket); + return this; + } + + + private Scope peek() throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + return stack.get(stack.size() - 1); + } + + private void replaceTop(Scope topOfStack) { + stack.set(stack.size() - 1, topOfStack); + } + + /** + * value + * + * @param value + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer value(Object value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + + if (value instanceof JSONArray) { + ((JSONArray) value).writeTo(this); + return this; + + } else if (value instanceof JSONObject) { + ((JSONObject) value).writeTo(this); + return this; + } + + beforeValue(); + + if (value == null + || value instanceof Boolean + || value == JSONObject.NULL) { + out.append(value); + + } else if (value instanceof Number) { + out.append(JSONObject.numberToString((Number) value)); + + } else { + string(value.toString()); + } + + return this; + } + + /** + * value + * + * @param value + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer value(boolean value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + /** + * value + * + * @param value + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer value(double value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(JSONObject.numberToString(value)); + return this; + } + + /** + * value + * + * @param value + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer value(long value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + private void string(String value) { + out.append("\""); + for (int i = 0, length = value.length(); i < length; i++) { + char c = value.charAt(i); + + /* + * From RFC 4627, "All Unicode characters may be placed within the + * quotation marks except for the characters that must be escaped: + * quotation mark, reverse solidus, and the control characters + * (U+0000 through U+001F)." + */ + switch (c) { + case '"': + case '\\': + case '/': + out.append('\\').append(c); + break; + + case '\t': + out.append("\\t"); + break; + + case '\b': + out.append("\\b"); + break; + + case '\n': + out.append("\\n"); + break; + + case '\r': + out.append("\\r"); + break; + + case '\f': + out.append("\\f"); + break; + + default: + if (c <= 0x1F) { + out.append(String.format("\\u%04x", (int) c)); + } else { + out.append(c); + } + break; + } + + } + out.append("\""); + } + + private void newline() { + if (indent == null) { + return; + } + + out.append("\n"); + for (int i = 0; i < stack.size(); i++) { + out.append(indent); + } + } + + /** + * key + * + * @param name + * @return JSONStringer + * @throws JSONException + */ + public JSONStringer key(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + beforeKey(); + string(name); + return this; + } + + private void beforeKey() throws JSONException { + Scope context = peek(); + if (context == Scope.NONEMPTY_OBJECT) { // first in object + out.append(','); + } else if (context != Scope.EMPTY_OBJECT) { // not in an object! + throw new JSONException("Nesting problem"); + } + newline(); + replaceTop(Scope.DANGLING_KEY); + } + + private void beforeValue() throws JSONException { + if (stack.isEmpty()) { + return; + } + + Scope context = peek(); + if (context == Scope.EMPTY_ARRAY) { // first in array + replaceTop(Scope.NONEMPTY_ARRAY); + newline(); + } else if (context == Scope.NONEMPTY_ARRAY) { // another in array + out.append(','); + newline(); + } else if (context == Scope.DANGLING_KEY) { // value for key + out.append(indent == null ? ":" : ": "); + replaceTop(Scope.NONEMPTY_OBJECT); + } else if (context != Scope.NULL) { + throw new JSONException("Nesting problem"); + } + } + + @Override + public String toString() { + return out.length() == 0 ? null : out.toString(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/json/JSONTokener.java b/entry/src/main/java/com/wordplat/quickstart/json/JSONTokener.java new file mode 100644 index 0000000000000000000000000000000000000000..5070b95216b6ece578237a35e7ee384ff6ce773f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/json/JSONTokener.java @@ -0,0 +1,560 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.json; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + + +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +/** + * Parses a JSON (RFC 4627) + * encoded string into the corresponding object. Most clients of + * this class will use only need the {@link #JSONTokener(String) constructor} + * and {@link #nextValue} method. Example usage:

+ * String json = "{"
+ *         + "  \"query\": \"Pizza\", "
+ *         + "  \"locations\": [ 94043, 90210 ] "
+ *         + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ * + *

For best interoperability and performance use JSON that complies with + * RFC 4627, such as that generated by {@link JSONStringer}. For legacy reasons + * this parser is lenient, so a successful parse does not indicate that the + * input string was valid JSON. All of the following syntax errors will be + * ignored: + *

    + *
  • End of line comments starting with {@code //} or {@code #} and ending + * with a newline character. + *
  • C-style comments starting with {@code /*} and ending with + * {@code *}{@code /}. Such comments may not be nested. + *
  • Strings that are unquoted or {@code 'single quoted'}. + *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. + *
  • Octal integers prefixed with {@code 0}. + *
  • Array elements separated by {@code ;}. + *
  • Unnecessary array separators. These are interpreted as if null was the + * omitted value. + *
  • Key-value pairs separated by {@code =} or {@code =>}. + *
  • Key-value pairs separated by {@code ;}. + *
+ * + *

Each tokener may be used to parse a single JSON string. Instances of this + * class are not thread safe. Although this class is nonfinal, it was not + * designed for inheritance and should not be subclassed. In particular, + * self-use by overrideable methods is not specified. See Effective Java + * Item 17, "Design and Document or inheritance or else prohibit it" for further + * information. + */ +public class JSONTokener { + + private final String in; + + private int pos; + + /** + * JSONTokener + * + * @param in + */ + public JSONTokener(String in) { + // consume an optional byte order mark (BOM) if it exists + if (in != null && in.startsWith("\ufeff")) { + in = in.substring(1); + } + this.in = in; + } + + /** + * nextValue + * + * @return Object + * @throws JSONException + */ + public Object nextValue() throws JSONException { + int c = nextCleanInternal(); + switch (c) { + case -1: + throw syntaxError("End of input"); + + case '{': + return readObject(); + + case '[': + return readArray(); + + case '\'': + case '"': + return nextString((char) c); + + default: + pos--; + return readLiteral(); + } + } + + private int nextCleanInternal() throws JSONException { + while (pos < in.length()) { + int c = in.charAt(pos++); + switch (c) { + case '\t': + case ' ': + case '\n': + case '\r': + continue; + + case '/': + if (pos == in.length()) { + return c; + } + + char peek = in.charAt(pos); + switch (peek) { + case '*': + // skip a /* c-style comment */ + pos++; + int commentEnd = in.indexOf("*/", pos); + if (commentEnd == -1) { + throw syntaxError("Unterminated comment"); + } + pos = commentEnd + 2; + continue; + + case '/': + // skip a // end-of-line comment + pos++; + skipToEndOfLine(); + continue; + + default: + return c; + } + + case '#': + /* + * Skip a # hash end-of-line comment. The JSON RFC doesn't + * specify this behavior, but it's required to parse + * existing documents. See http://b/2571423. + */ + skipToEndOfLine(); + continue; + + default: + return c; + } + } + + return -1; + } + + private void skipToEndOfLine() { + for (; pos < in.length(); pos++) { + char c = in.charAt(pos); + if (c == '\r' || c == '\n') { + pos++; + break; + } + } + } + + /** + * nextString + * + * @param quote + * @return String + * @throws JSONException + */ + public String nextString(char quote) throws JSONException { + /* + * For strings that are free of escape sequences, we can just extract + * the result as a substring of the input. But if we encounter an escape + * sequence, we need to use a StringBuilder to compose the result. + */ + StringBuilder builder = null; + + /* the index of the first character not yet appended to the builder. */ + int start = pos; + + while (pos < in.length()) { + int c = in.charAt(pos++); + if (c == quote) { + if (builder == null) { + // a new string avoids leaking memory + return new String(in.substring(start, pos - 1)); + } else { + builder.append(in, start, pos - 1); + return builder.toString(); + } + } + + if (c == '\\') { + if (pos == in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(in, start, pos - 1); + builder.append(readEscapeCharacter()); + start = pos; + } + } + + throw syntaxError("Unterminated string"); + } + + private char readEscapeCharacter() throws JSONException { + char escaped = in.charAt(pos++); + switch (escaped) { + case 'u': + if (pos + 4 > in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + String hex = in.substring(pos, pos + 4); + pos += 4; + return (char) Integer.parseInt(hex, 16); + + case 't': + return '\t'; + + case 'b': + return '\b'; + + case 'n': + return '\n'; + + case 'r': + return '\r'; + + case 'f': + return '\f'; + + case '\'': + case '"': + case '\\': + default: + return escaped; + } + } + + private Object readLiteral() throws JSONException { + String literal = nextToInternal("{}[]/\\:,=;# \t\f"); + + if (literal.length() == 0) { + throw syntaxError("Expected literal value"); + } else if ("null".equalsIgnoreCase(literal)) { + return JSONObject.NULL; + } else if ("true".equalsIgnoreCase(literal)) { + return Boolean.TRUE; + } else if ("false".equalsIgnoreCase(literal)) { + return Boolean.FALSE; + } + + /* try to parse as an integral type... */ + if (literal.indexOf('.') == -1) { + int base = 10; + String number = literal; + if (number.startsWith("0x") || number.startsWith("0X")) { + number = number.substring(2); + base = 16; + } else if (number.startsWith("0") && number.length() > 1) { + number = number.substring(1); + base = 8; + } + try { + long longValue = Long.parseLong(number, base); + if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { + return (int) longValue; + } else { + return longValue; + } + } catch (NumberFormatException e) { + /* + * This only happens for integral numbers greater than + * Long.MAX_VALUE, numbers in exponential form (5e-10) and + * unquoted strings. Fall through to try floating point. + */ + LogUtil.e("error:" + e); + } + } + + /* ...next try to parse as a floating point... */ + try { + return Double.valueOf(literal); + } catch (NumberFormatException ignored) { + LogUtil.e("error:" + ignored); + } + + /* ... finally give up. We have an unquoted string */ + return new String(literal); // a new string avoids leaking memory + } + + private String nextToInternal(String excluded) { + int start = pos; + for (; pos < in.length(); pos++) { + char c = in.charAt(pos); + if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { + return in.substring(start, pos); + } + } + return in.substring(start); + } + + private JSONObject readObject() throws JSONException { + JSONObject result = new JSONObject(); + + /* Peek to see if this is the empty object. */ + int first = nextCleanInternal(); + if (first == '}') { + return result; + } else if (first != -1) { + pos--; + } + + while (true) { + Object name = nextValue(); + if (!(name instanceof String)) { + if (name == null) { + throw syntaxError("Names cannot be null"); + } else { + throw syntaxError("Names must be strings, but " + name + + " is of type " + name.getClass().getName()); + } + } + + /* + * Expect the name/value separator to be either a colon ':', an + * equals sign '=', or an arrow "=>". The last two are bogus but we + * include them because that's what the original implementation did. + */ + int separator = nextCleanInternal(); + if (separator != ':' && separator != '=') { + throw syntaxError("Expected ':' after " + name); + } + if (pos < in.length() && in.charAt(pos) == '>') { + pos++; + } + + result.put((String) name, nextValue()); + + switch (nextCleanInternal()) { + case '}': + return result; + case ';': + case ',': + continue; + default: + throw syntaxError("Unterminated object"); + } + } + } + + private JSONArray readArray() throws JSONException { + JSONArray result = new JSONArray(); + + /* to cover input that ends with ",]". */ + boolean hasTrailingSeparator = false; + + while (true) { + switch (nextCleanInternal()) { + case -1: + throw syntaxError("Unterminated array"); + case ']': + if (hasTrailingSeparator) { + result.put(null); + } + return result; + case ',': + case ';': + /* A separator without a value first means "null". */ + result.put(null); + hasTrailingSeparator = true; + continue; + default: + pos--; + } + + result.put(nextValue()); + + switch (nextCleanInternal()) { + case ']': + return result; + case ',': + case ';': + hasTrailingSeparator = true; + continue; + default: + throw syntaxError("Unterminated array"); + } + } + } + + /** + * syntaxError + * + * @param message + * @return JSONException + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this); + } + + @Override + public String toString() { + // consistent with the original implementation + return " at character " + pos + " of " + in; + } + + /** + * more + * + * @return boolean + */ + public boolean more() { + return pos < in.length(); + } + + /** + * next + * + * @return char + */ + public char next() { + return pos < in.length() ? in.charAt(pos++) : '\0'; + } + + /** + * next + * + * @param c + * @return char + * @throws JSONException + */ + public char next(char c) throws JSONException { + char result = next(); + if (result != c) { + throw syntaxError("Expected " + c + " but was " + result); + } + return result; + } + + /** + * nextClean + * + * @return char + * @throws JSONException + */ + public char nextClean() throws JSONException { + int nextCleanInt = nextCleanInternal(); + return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; + } + + /** + * next + * + * @param length + * @return String + * @throws JSONException + */ + public String next(int length) throws JSONException { + if (pos + length > in.length()) { + throw syntaxError(length + " is out of bounds"); + } + String result = in.substring(pos, pos + length); + pos += length; + return result; + } + + /** + * nextTo + * + * @param excluded + * @return String + * @throws NullPointerException + */ + public String nextTo(String excluded) { + if (excluded == null) { + throw new NullPointerException("excluded == null"); + } + return nextToInternal(excluded).trim(); + } + + /** + * nextTo + * + * @param excluded + * @return String + */ + public String nextTo(char excluded) { + return nextToInternal(String.valueOf(excluded)).trim(); + } + + /** + * skipPast + * + * @param thru + */ + public void skipPast(String thru) { + int thruStart = in.indexOf(thru, pos); + pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); + } + + /** + * skipTo + * + * @param to + * @return char + */ + public char skipTo(char to) { + int index = in.indexOf(to, pos); + if (index != -1) { + pos = index; + return to; + } else { + return '\0'; + } + } + + /** + * back + */ + public void back() { + if (--pos == -1) { + pos = 0; + } + } + + /** + * dehexchar + * + * @param hex + * @return int + */ + public static int dehexchar(char hex) { + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } else if (hex >= 'A' && hex <= 'F') { + return hex - 'A' + 10; + } else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } else { + return -1; + } + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java b/entry/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java similarity index 61% rename from app/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java index b120cb6438fad147b8800c2fb9e1f368410ff931..f1f81036cee90e9edd25ea88351f27830cc0658a 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/BasePresenter.java @@ -1,18 +1,12 @@ package com.wordplat.quickstart.mvp; -import android.text.TextUtils; -import android.util.Log; -import android.widget.EditText; import com.wordplat.quickstart.BuildConfig; -import com.wordplat.quickstart.R; import com.wordplat.quickstart.app.AppRuntime; -import com.wordplat.quickstart.mvp.exception.NetworkTimeOutException; -import com.wordplat.quickstart.mvp.exception.NoNetworkException; -import com.wordplat.quickstart.mvp.exception.ResultEmptyException; -import com.wordplat.quickstart.mvp.exception.ResultFailedException; -import com.wordplat.quickstart.mvp.exception.ResultParseException; +import com.wordplat.quickstart.mvp.exception.*; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import ohos.agp.components.TextField; import rx.Subscription; import rx.subscriptions.CompositeSubscription; @@ -21,8 +15,9 @@ import rx.subscriptions.CompositeSubscription; *

Date: 2017/4/11

* * @author afon + * @param + * @since 2017-04-11 */ - public abstract class BasePresenter { protected T baseView; @@ -37,7 +32,7 @@ public abstract class BasePresenter { public void attachView(T baseView) { this.baseView = baseView; - if(compositeSubscription == null) { + if (compositeSubscription == null) { compositeSubscription = new CompositeSubscription(); initSubscription(); } @@ -47,16 +42,26 @@ public abstract class BasePresenter { * 通常在 onPause 时调用此方法 */ public void detachView() { - if(compositeSubscription != null) { + if (compositeSubscription != null) { compositeSubscription.unsubscribe(); compositeSubscription = null; } } - protected void initSubscription() {} + /** + * initSubscription + * + */ + protected void initSubscription() { + } + /** + * addSubscription + * + * @param subscription + */ protected void addSubscription(Subscription subscription) { - if(compositeSubscription == null) { + if (compositeSubscription == null) { subscription.unsubscribe(); } else { compositeSubscription.add(subscription); @@ -67,49 +72,67 @@ public abstract class BasePresenter { * 取消所有订阅 */ public void cancel() { - if(compositeSubscription != null) { + if (compositeSubscription != null) { compositeSubscription.unsubscribe(); compositeSubscription = new CompositeSubscription(); } } - public boolean isEmpty(EditText editText) { - if(editText == null || editText.getText() == null || TextUtils.isEmpty(editText.getText().toString())) { + /** + * isEmpty + * + * @param editText + * @return boolean + */ + public boolean isEmpty(TextField editText) { + if (editText == null || editText.getText() == null || TextUtils.isEmpty(editText.getText().toString())) { return true; } return false; } + /** + * getString + * + * @param stringResId + * @return String + */ public String getString(int stringResId) { if (stringResId != 0) { if (AppRuntime.sContext != null) { - return AppRuntime.sContext.getResources().getString(stringResId); + return AppRuntime.sContext.getString(stringResId); } } return ""; } + /** + * handleError + * + * @param requestCode + * @param throwable + */ protected void handleError(int requestCode, Throwable throwable) { final String errMessage; - if(throwable instanceof NoNetworkException) { // 没有网络 + if (throwable instanceof NoNetworkException) { // 没有网络 errMessage = "onNoNetworkError --> " + requestCode; baseView.onNoNetworkError(requestCode); - } else if(throwable instanceof NetworkTimeOutException) { // 网络超时 + } else if (throwable instanceof NetworkTimeOutException) { // 网络超时 errMessage = "onNetworkTimeOutError --> " + requestCode; baseView.onNetworkTimeOutError(requestCode); - } else if(throwable instanceof ResultParseException) { // 服务器返回的 JSON 数据解析错误 + } else if (throwable instanceof ResultParseException) { // 服务器返回的 JSON 数据解析错误 errMessage = "onResultParseError --> " + requestCode; baseView.onResultParseError(requestCode); - } else if(throwable instanceof ResultEmptyException) { // 服务器返回结果为空 + } else if (throwable instanceof ResultEmptyException) { // 服务器返回结果为空 errMessage = "onResultEmpty --> " + requestCode; baseView.onResultEmpty(requestCode); - } else if(throwable instanceof ResultFailedException) { // 服务器返回错误码 + } else if (throwable instanceof ResultFailedException) { // 服务器返回错误码 errMessage = "onResultFailed --> " + requestCode; baseView.onResultFailed(requestCode, ((ResultFailedException) throwable).getErrCode(), @@ -117,12 +140,7 @@ public abstract class BasePresenter { } else { // 其它不知道的错误,除非在设置UI时发生了错误,否则不会走到这里 errMessage = "Warning_Unknow -->" + requestCode; - baseView.onShowWarning(requestCode, R.string.Warning_Unknow); - } - - if(BuildConfig.DEBUG) { - Log.e("BasePresenter", "##d handleError: " + errMessage); - throwable.printStackTrace(); + baseView.onResultEmpty(requestCode); } } } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java similarity index 85% rename from app/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java index 0cf4932f7941d90238131547e4731c293cfbd10f..ec9f127000b09f48b3f292939cde09e2ca724e4b 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseRequest.java @@ -1,36 +1,40 @@ package com.wordplat.quickstart.mvp; -import android.text.TextUtils; -import android.util.Log; import com.alibaba.fastjson.JSON; import com.wordplat.quickstart.BuildConfig; -import com.wordplat.quickstart.app.AppRuntime; import com.wordplat.quickstart.bean.response.IResultResponse; import com.wordplat.quickstart.mvp.exception.NetworkTimeOutException; -import com.wordplat.quickstart.mvp.exception.NoNetworkException; import com.wordplat.quickstart.mvp.exception.ResultEmptyException; import com.wordplat.quickstart.mvp.exception.ResultFailedException; -import com.wordplat.quickstart.utils.AppUtils; - -import org.xutils.http.HttpMethod; -import org.xutils.http.RequestParams; -import org.xutils.x; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.HttpMethod; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.x; import java.net.SocketTimeoutException; import java.util.List; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; import rx.Subscriber; + /** *

BaseRequest

*

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class BaseRequest { + static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "BaseRequest"); private static final String TAG = "BaseRequest"; + private static final int NUM_1001 = 1001; + + private BaseRequest() { + } /** * 网络请求 返回数据是一个 ServerResponse 对象 @@ -38,6 +42,7 @@ public class BaseRequest { * @param subscriber subscriber * @param requestParams 请求参数 * @param responseObject 返回数据的 Class 类型 + * @param */ protected static void requestServer(Subscriber subscriber, RequestParams requestParams, @@ -45,6 +50,15 @@ public class BaseRequest { requestServer(subscriber, requestParams, responseObject, HttpMethod.POST); } + /** + * requestServer + * + * @param subscriber + * @param requestParams + * @param responseObject + * @param httpMethod + * @param + */ protected static void requestServer(Subscriber subscriber, RequestParams requestParams, Class responseObject, @@ -62,6 +76,7 @@ public class BaseRequest { * @param subscriber subscriber * @param requestParams 请求参数 * @param responseObject 返回数据的 Class 类型 + * @param */ protected static void requestObject(Subscriber subscriber, RequestParams requestParams, @@ -69,6 +84,15 @@ public class BaseRequest { requestObject(subscriber, requestParams, responseObject, HttpMethod.POST); } + /** + * requestObject + * + * @param subscriber + * @param requestParams + * @param responseObject + * @param httpMethod + * @param + */ protected static void requestObject(Subscriber subscriber, RequestParams requestParams, Class responseObject, @@ -86,6 +110,7 @@ public class BaseRequest { * @param subscriber subscriber * @param requestParams 请求参数 * @param responseObject 返回数据的 Class 类型 + * @param */ protected static void requestArray(Subscriber> subscriber, RequestParams requestParams, @@ -93,6 +118,15 @@ public class BaseRequest { requestArray(subscriber, requestParams, responseObject, HttpMethod.POST); } + /** + * requestArray + * + * @param subscriber + * @param requestParams + * @param responseObject + * @param httpMethod + * @param + */ protected static void requestArray(Subscriber> subscriber, RequestParams requestParams, Class responseObject, @@ -116,10 +150,6 @@ public class BaseRequest { RequestParams requestParams, Class responseObject, HttpMethod httpMethod) { - if (!AppUtils.isConnected(AppRuntime.sContext)) { - subscriber.onError(new NoNetworkException()); - return null; - } try { R result = x.http().requestSync(httpMethod, requestParams, responseObject); @@ -127,7 +157,7 @@ public class BaseRequest { subscriber.onError(new ResultEmptyException()); } else if (result.getResultCode() != 0) { - if (result.getResultCode() == 1001) { // 单独抽出服务器返回为空数据时的错误码 + if (result.getResultCode() == NUM_1001) { // 单独抽出服务器返回为空数据时的错误码 subscriber.onError(new ResultEmptyException()); } else { @@ -147,14 +177,10 @@ public class BaseRequest { RequestParams requestParams, Class responseObject, HttpMethod httpMethod) { - if (!AppUtils.isConnected(AppRuntime.sContext)) { - subscriber.onError(new NoNetworkException()); - return null; - } try { String result = x.http().requestSync(httpMethod, requestParams, String.class); if (BuildConfig.DEBUG) { - Log.i(TAG, "##d 服务器返回数据: " + result); + HiLog.info(LABEL, "##d 服务器返回数据: " + result); } if (responseObject == String.class) { @@ -184,14 +210,10 @@ public class BaseRequest { RequestParams requestParams, Class responseObject, HttpMethod httpMethod) { - if (!AppUtils.isConnected(AppRuntime.sContext)) { - subscriber.onError(new NoNetworkException()); - return null; - } try { String result = x.http().requestSync(httpMethod, requestParams, String.class); if (BuildConfig.DEBUG) { - Log.i(TAG, "##d 服务器返回数据: " + result); + HiLog.info(LABEL, "##d 服务器返回数据: " + result); } if (TextUtils.isEmpty(result)) { diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BaseView.java b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseView.java similarity index 73% rename from app/src/main/java/com/wordplat/quickstart/mvp/BaseView.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/BaseView.java index d5c8960fa31f82901bcc29e9029b965f43c333de..a4cf4b69eb92c953c0183d925b99ce8a38a92ea9 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BaseView.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseView.java @@ -3,15 +3,15 @@ package com.wordplat.quickstart.mvp; /** *

* requestCode 请求编码 的意思是: - * 对每一次 presenter 的调用指定一个唯一编码。 - * 比如,一个 Activity 中使用了两个以上的 presenter 方法,在 Activity 中 implement 了本接口, - * 那么此时如果返回结果,就需根据请求编码来作对应的UI呈现。 + * 对每一次 presenter 的调用指定一个唯一编码。 + * 比如,一个 Activity 中使用了两个以上的 presenter 方法,在 Activity 中 implement 了本接口, + * 那么此时如果返回结果,就需根据请求编码来作对应的UI呈现。 *

*

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public interface BaseView { /** @@ -46,8 +46,8 @@ public interface BaseView { * 服务器返回失败、返回错误 * * @param requestCode 请求编码 - * @param errCode 服务器返回的错误编码 - * @param errMessage 服务器返回的错误信息 + * @param errCode 服务器返回的错误编码 + * @param errMessage 服务器返回的错误信息 */ void onResultFailed(int requestCode, int errCode, String errMessage); @@ -61,7 +61,7 @@ public interface BaseView { /** * 显示一个警告信息 * - * @param requestCode 请求编码 + * @param requestCode 请求编码 * @param errMessageResId 要显示的错误 String 的资源ID */ void onShowWarning(int requestCode, int errMessageResId); diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java similarity index 53% rename from app/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java index 585e7ca59151d48278c27ece0c5f226b081a0429..65174a6e017ee3aef9183459bd27318bdb29e534 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/BaseViewListener.java @@ -1,31 +1,28 @@ package com.wordplat.quickstart.mvp; -import android.text.TextUtils; -import android.widget.Toast; - -import com.wordplat.quickstart.R; +import com.wordplat.quickstart.ResourceTable; import com.wordplat.quickstart.app.AppRuntime; +import com.wordplat.quickstart.utils.ToastViewDialog; +import com.wordplat.quickstart.xutils.common.util.TextUtils; /** *

BaseViewListener

*

Date: 2017/5/15

* * @author afon + * @since 2017-04-11 */ - public class BaseViewListener implements BaseView { @Override public void onNoNetworkError(int requestCode) { - Toast.makeText(AppRuntime.sContext, - AppRuntime.sContext.getResources().getString(R.string.Warning_No_Network), - Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(AppRuntime.sContext, AppRuntime.sContext.getString( + ResourceTable.String_Warning_No_Network)); } @Override public void onNetworkTimeOutError(int requestCode) { - Toast.makeText(AppRuntime.sContext, - AppRuntime.sContext.getResources().getString(R.string.Warning_Network_Timeout), - Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(AppRuntime.sContext, AppRuntime.sContext.getString( + ResourceTable.String_Warning_Network_Timeout)); } @Override @@ -41,7 +38,7 @@ public class BaseViewListener implements BaseView { @Override public void onResultFailed(int requestCode, int errCode, String errMessage) { if (!TextUtils.isEmpty(errMessage)) { - Toast.makeText(AppRuntime.sContext, errMessage, Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(AppRuntime.sContext, errMessage); } } @@ -52,8 +49,6 @@ public class BaseViewListener implements BaseView { @Override public void onShowWarning(int requestCode, int errMessageResId) { - Toast.makeText(AppRuntime.sContext, - AppRuntime.sContext.getResources().getString(errMessageResId), - Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(AppRuntime.sContext, AppRuntime.sContext.getString(errMessageResId)); } } diff --git a/entry/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java b/entry/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..58a1114cdcb648c048ccc9a440ba983cba4053c4 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/BtcChinaPresenter.java @@ -0,0 +1,31 @@ +package com.wordplat.quickstart.mvp; + +import com.wordplat.quickstart.bean.BtcBean; + +import java.util.List; + + +/** + *

BtcChinaPresenter

+ *

Date: 2017/4/16

+ * + * @author afon + * @since 2017-04-11 + */ +public class BtcChinaPresenter extends BasePresenter { + + private List btcList; + + /** + * getSimple + * + * @param requestCode + */ + public void getSimple(final int requestCode) { + handleError(requestCode, new Throwable("服务器错误")); + } + + public List getBtcList() { + return btcList; + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java b/entry/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java similarity index 54% rename from app/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java index 35ac18f5de97ec3c90858656e7978bedfdd11f5c..359bc008f560a0d3430871f47957fc043d8925c1 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/LoadingView.java @@ -5,13 +5,27 @@ package com.wordplat.quickstart.mvp; *

Date: 2017/5/15

* * @author afon + * @since 2017-04-11 */ - public interface LoadingView extends BaseView { - + /** + * onStartRequest + * + * @param requestCode + */ void onStartRequest(int requestCode); + /** + * onFinishRequest + * + * @param requestCode + */ void onFinishRequest(int requestCode); + /** + * onSuccess + * + * @param requestCode 请求编码 + */ void onSuccess(int requestCode); } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java b/entry/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java similarity index 94% rename from app/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java index 74f554d27ed121f19e22d0ddc07583af2bec5d80..8f57b7828a1cbd74d986b9fdbb30b9b51640db17 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/LoadingViewListener.java @@ -5,8 +5,8 @@ package com.wordplat.quickstart.mvp; *

Date: 2017/5/22

* * @author afon + * @since 2017-05-22 */ - public class LoadingViewListener extends BaseViewListener implements LoadingView { @Override diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java b/entry/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java similarity index 44% rename from app/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java index 378f9c61ae7684a85f6945f8be5bdf6de91c9f45..5f8b3cb878d87157c7b452351a25627ec205dbaf 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/StockPresenter.java @@ -1,39 +1,61 @@ package com.wordplat.quickstart.mvp; +import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.quickstart.bean.KLineBean; +import com.wordplat.quickstart.utils.ToastViewDialog; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.Component; +import ohos.app.dispatcher.TaskDispatcher; import java.util.Calendar; import java.util.Date; import java.util.List; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; - /** *

StockPresenter

*

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class StockPresenter extends BasePresenter { - private static final int LOAD_BETWEEN_DAYS = 100; // 日 K 一次加载多少天 - private static final int LOAD_BETWEEN_WEEK = 50; // 周 K 一次加载多少周 - private static final int LOAD_BETWEEN_MONTH = 50; // 月 K 一次加载多少个月 + /** + * 日 K 一次加载多少天 + */ + private static final int LOAD_BETWEEN_DAYS = 100; + /** + * 周 K 一次加载多少周 + */ + private static final int LOAD_BETWEEN_WEEK = 50; + /** + * 月 K 一次加载多少个月 + */ + private static final int LOAD_BETWEEN_MONTH = 50; private final Calendar currentBeginTime = Calendar.getInstance(); private final Calendar currentEndTime = Calendar.getInstance(); private List kLineList; + /** + * KLineType + * + */ public enum KLineType { - DAY, - WEEK, - MONTH + /** + * DAY ,WEEK ,MONTH + */ + DAY, WEEK, MONTH } + /** + * loadFirst + * + * @param reqeustCode + * @param stockCode + * @param kLineType + */ public void loadFirst(int reqeustCode, String stockCode, KLineType kLineType) { currentEndTime.setTime(new Date()); currentBeginTime.setTime(currentEndTime.getTime()); @@ -41,6 +63,13 @@ public class StockPresenter extends BasePresenter { loadPrev(reqeustCode, stockCode, kLineType); } + /** + * loadPrev + * + * @param requestCode + * @param stockCode + * @param kLineType + */ public void loadPrev(final int requestCode, String stockCode, final KLineType kLineType) { baseView.onStartRequest(requestCode); @@ -48,36 +77,17 @@ public class StockPresenter extends BasePresenter { calendar.setTime(currentBeginTime.getTime()); computeNextTime(calendar, kLineType, true); - Subscription subscription = StockApiRequest.getKLine(stockCode, - calendar.getTime(), - currentBeginTime.getTime(), - getKLineType(kLineType)) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1>() { - @Override - public void call(List response) { - baseView.onFinishRequest(requestCode); - - if (response == null || response.size() == 0) { - baseView.onResultEmpty(requestCode); - } else { - computeNextTime(currentBeginTime, kLineType, true); - - kLineList = response; - baseView.onSuccess(requestCode); - } - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - baseView.onFinishRequest(requestCode); - handleError(requestCode, throwable); - } - }); - - addSubscription(subscription); + baseView.onFinishRequest(requestCode); + handleError(requestCode, new Throwable("服务器出错")); } + /** + * loadNext + * + * @param requestCode + * @param stockCode + * @param kLineType + */ public void loadNext(final int requestCode, String stockCode, final KLineType kLineType) { baseView.onStartRequest(requestCode); @@ -85,40 +95,25 @@ public class StockPresenter extends BasePresenter { calendar.setTime(currentEndTime.getTime()); computeNextTime(calendar, kLineType, false); - Subscription subscription = StockApiRequest.getKLine(stockCode, - currentEndTime.getTime(), - calendar.getTime(), - getKLineType(kLineType)) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1>() { - @Override - public void call(List response) { - baseView.onFinishRequest(requestCode); - - if (response == null || response.size() == 0) { - baseView.onResultEmpty(requestCode); - } else { - computeNextTime(currentEndTime, kLineType, false); - - kLineList = response; - baseView.onSuccess(requestCode); - } - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - baseView.onFinishRequest(requestCode); - handleError(requestCode, throwable); - } - }); - - addSubscription(subscription); + baseView.onFinishRequest(requestCode); + handleError(requestCode, new Throwable("服务器出错")); } + /** + * getkLineList + * + * @return KLineBean + */ public List getkLineList() { return kLineList; } + /** + * getKLineType + * + * @param kLineType + * @return + */ private String getKLineType(KLineType kLineType) { String type = "d"; switch (kLineType) { @@ -133,13 +128,14 @@ public class StockPresenter extends BasePresenter { case MONTH: type = "M"; break; + default:break; } return type; } - private void computeNextTime(Calendar calendar, KLineType kLineType, boolean reverse) { - switch (kLineType) { + private void computeNextTime(Calendar calendar, KLineType KlineType, boolean reverse) { + switch (KlineType) { case DAY: calendar.add(Calendar.DAY_OF_MONTH, reverse ? -LOAD_BETWEEN_DAYS : LOAD_BETWEEN_DAYS); break; @@ -151,6 +147,8 @@ public class StockPresenter extends BasePresenter { case MONTH: calendar.add(Calendar.MONTH, reverse ? -LOAD_BETWEEN_MONTH : LOAD_BETWEEN_MONTH); break; + default: + break; } } } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java similarity index 90% rename from app/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java index 59f4e645bd22928ae46b1a8d3cf045639d696432..8d9f5068ebe894ce34175e75dc8f43164698321b 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/NetworkTimeOutException.java @@ -5,7 +5,7 @@ package com.wordplat.quickstart.mvp.exception; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class NetworkTimeOutException extends Exception { } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java similarity index 89% rename from app/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java index f46bca357f494e9f7879ff6c3819e4c29ce2046f..32540201e8ea579b30520eab4b2c726b8b373dc1 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/NoNetworkException.java @@ -5,7 +5,7 @@ package com.wordplat.quickstart.mvp.exception; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class NoNetworkException extends Exception { } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java similarity index 90% rename from app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java index 50bee4fae8753c63dcb7378e021d596558904cd7..15b39d628e1c7bf8d3c18f6cb6f7d5c3f9646071 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultEmptyException.java @@ -5,7 +5,7 @@ package com.wordplat.quickstart.mvp.exception; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class ResultEmptyException extends Exception { } diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java similarity index 81% rename from app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java index b2a64ee2a83ccb96c89a6b4df41236abb3335835..55684d26fc22db20c9491cb5b7471cc7bbe149b4 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultFailedException.java @@ -5,14 +5,20 @@ package com.wordplat.quickstart.mvp.exception; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class ResultFailedException extends Exception { private int errCode; private String errMessage; + /** + * ResultFailedException + * + * @param errCode + * @param errMessage + */ public ResultFailedException(int errCode, String errMessage) { this.errCode = errCode; this.errMessage = errMessage; diff --git a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java similarity index 90% rename from app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java rename to entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java index a49c32013611e609dc2dea5c8e6baaf9ff019e78..ccfd250d0161c6a31b55672c5b71cdb1dde4f67a 100644 --- a/app/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java +++ b/entry/src/main/java/com/wordplat/quickstart/mvp/exception/ResultParseException.java @@ -5,7 +5,7 @@ package com.wordplat.quickstart.mvp.exception; *

Date: 2017/4/11

* * @author afon + * @since 2017-04-11 */ - public class ResultParseException extends Exception { } \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/slice/Disable_Left_And_Right_Refresh_Slice.java b/entry/src/main/java/com/wordplat/quickstart/slice/Disable_Left_And_Right_Refresh_Slice.java new file mode 100644 index 0000000000000000000000000000000000000000..affe80fa9c2a710fb38e2f367e5aad423f88382e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/slice/Disable_Left_And_Right_Refresh_Slice.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.wordplat.quickstart.slice; + +import ohos.aafwk.content.Intent; + +/** + * Disable_Left_And_Right_Refresh_Slice + * + * @since 2021-04-30 + */ +public class Disable_Left_And_Right_Refresh_Slice extends Enable_Left_And_Right_Refresh_Slice { + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + kLineLayout.getKLineView().setEnableLeftRefresh(false); + kLineLayout.getKLineView().setEnableRightRefresh(false); + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/Enable_Left_And_Right_Refresh_Activity.java b/entry/src/main/java/com/wordplat/quickstart/slice/Enable_Left_And_Right_Refresh_Slice.java similarity index 40% rename from app/src/main/java/com/wordplat/quickstart/activity/Enable_Left_And_Right_Refresh_Activity.java rename to entry/src/main/java/com/wordplat/quickstart/slice/Enable_Left_And_Right_Refresh_Slice.java index 2dd4e4cf860515f1df8f57cf4a45bdaa06e59ed0..99c6a3fc9b39a06a37b2e5df7ff702acfb2fe5c8 100644 --- a/app/src/main/java/com/wordplat/quickstart/activity/Enable_Left_And_Right_Refresh_Activity.java +++ b/entry/src/main/java/com/wordplat/quickstart/slice/Enable_Left_And_Right_Refresh_Slice.java @@ -1,19 +1,20 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Animatable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.wordplat.quickstart.R; +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.slice; + import com.wordplat.ikvstockchart.InteractiveKLineLayout; import com.wordplat.ikvstockchart.KLineHandler; import com.wordplat.ikvstockchart.compat.PerformenceAnalyser; @@ -22,165 +23,157 @@ import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.entry.StockDataTest; import com.wordplat.ikvstockchart.render.KLineRender; - -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; - -import java.io.InputStream; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; +import com.wordplat.quickstart.utils.ToastViewDialog; + +import ohos.aafwk.content.Intent; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.agp.utils.RectFloat; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.global.resource.ResourceManager; +import ohos.multimodalinput.event.TouchEvent; + +import java.io.IOException; import java.util.ArrayList; import java.util.List; /** - *

Enable_Left_And_Right_Refresh_Activity

- *

Date: 2017/3/10

+ * Enable_Left_And_Right_Refresh_Slice * - * @author afon + * @since 2021-04-22 */ - -@ContentView(R.layout.activity_left_and_right_refresh) -public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { - private static final String TAG = "Activity"; - - @ViewInject(R.id.kLineLayout) InteractiveKLineLayout kLineLayout = null; - @ViewInject(R.id.MA_Text) private TextView MA_Text = null; - @ViewInject(R.id.StockIndex_Text) private TextView StockIndex_Text = null; - @ViewInject(R.id.Volume_Text) private TextView Volume_Text = null; - @ViewInject(R.id.Left_Loading_Image) private ImageView Left_Loading_Image = null; - @ViewInject(R.id.Right_Loading_Image) private ImageView Right_Loading_Image = null; +public class Enable_Left_And_Right_Refresh_Slice extends BaseAbilitySlice { + + InteractiveKLineLayout kLineLayout; + private Text MA_Text; + private Text StockIndex_Text; + private Text Volume_Text; + private Image Left_Loading_Image; + private Image Right_Loading_Image; + private RadioContainer But_Group; + private RadioButton MACD_But; + private RadioButton RSI_But; + private RadioButton KDJ_But; + private RadioButton BOLL_But; private EntrySet entrySet; private int loadStartPos = 5500; private int loadEndPos = 6000; private int loadCount = 100; + /** + * 动画效果 mAnimator + */ + private AnimatorProperty mAnimator; + @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_slice_left_and_right_refresh); + initViews(); initUI(); loadKLineData(); } + /** + * 初始化view + */ + private void initViews() { + kLineLayout = (InteractiveKLineLayout) findComponentById(ResourceTable.Id_kLineLayout); + MA_Text = (Text) findComponentById(ResourceTable.Id_MA_Text); + StockIndex_Text = (Text) findComponentById(ResourceTable.Id_StockIndex_Text); + Volume_Text = (Text) findComponentById(ResourceTable.Id_Volume_Text); + Left_Loading_Image = (Image) findComponentById(ResourceTable.Id_Left_Loading_Image); + Right_Loading_Image = (Image) findComponentById(ResourceTable.Id_Right_Loading_Image); + + But_Group = (RadioContainer) findComponentById(ResourceTable.Id_But_Group); + MACD_But = (RadioButton) findComponentById(ResourceTable.Id_MACD_But); + RSI_But = (RadioButton) findComponentById(ResourceTable.Id_RSI_But); + KDJ_But = (RadioButton) findComponentById(ResourceTable.Id_KDJ_But); + BOLL_But = (RadioButton) findComponentById(ResourceTable.Id_BOLL_But); + } + private void initUI() { + kLineLayout.setClick(But_Group, MACD_But, RSI_But, KDJ_But, BOLL_But); kLineLayout.setKLineHandler(new KLineHandler() { @Override public void onHighlight(Entry entry, int entryIndex, float x, float y) { final SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); - - String maString = String.format(getResources().getString(R.string.ma_highlight), + String maString = null; + String volumeString = null; + String str = null; + maString = String.format(getString(ResourceTable.String_ma_highlight), entry.getMa5(), entry.getMa10(), entry.getMa20()); - - MA_Text.setText(getSpannableString(maString, - sizeColor.getMa5Color(), - sizeColor.getMa10Color(), - sizeColor.getMa20Color())); - - String volumeString = String.format(getResources().getString(R.string.volume_highlight), + volumeString = String.format(getString(ResourceTable.String_volume_highlight), entry.getVolumeMa5(), entry.getVolumeMa10()); - Volume_Text.setText(getSpannableString(volumeString, - sizeColor.getMa5Color(), - sizeColor.getMa10Color(), - 0)); - - SpannableString spanString = new SpannableString(""); if (kLineLayout.isShownMACD()) { - String str = String.format(getResources().getString(R.string.macd_highlight), + str = String.format(getString(ResourceTable.String_macd_highlight), entry.getDiff(), entry.getDea(), entry.getMacd()); - - spanString = getSpannableString(str, - sizeColor.getDiffLineColor(), - sizeColor.getDeaLineColor(), - sizeColor.getMacdHighlightTextColor()); - } else if (kLineLayout.isShownKDJ()) { - String str = String.format(getResources().getString(R.string.kdj_highlight), + str = String.format(getString(ResourceTable.String_kdj_highlight), entry.getK(), entry.getD(), entry.getJ()); - - spanString = getSpannableString(str, - sizeColor.getKdjKLineColor(), - sizeColor.getKdjDLineColor(), - sizeColor.getKdjJLineColor()); - } else if (kLineLayout.isShownRSI()) { - String str = String.format(getResources().getString(R.string.rsi_highlight), + + str = String.format(getString(ResourceTable.String_rsi_highlight), entry.getRsi1(), entry.getRsi2(), entry.getRsi3()); - - spanString = getSpannableString(str, - sizeColor.getRsi1LineColor(), - sizeColor.getRsi2LineColor(), - sizeColor.getRsi3LineColor()); - } else if (kLineLayout.isShownBOLL()) { - String str = String.format(getResources().getString(R.string.boll_highlight), + str = String.format(getString(ResourceTable.String_boll_highlight), entry.getMb(), entry.getUp(), entry.getDn()); - - spanString = getSpannableString(str, - sizeColor.getBollMidLineColor(), - sizeColor.getBollUpperLineColor(), - sizeColor.getBollLowerLineColor()); } - StockIndex_Text.setText(spanString); + + + MA_Text.setText(maString); + Volume_Text.setText(volumeString); + StockIndex_Text.setText(str); } @Override public void onCancelHighlight() { - String maString = getResources().getString(R.string.ma_normal); + String maString = getString(ResourceTable.String_ma_normal); MA_Text.setText(maString); Volume_Text.setText(""); String stockIndexString = ""; if (kLineLayout.isShownMACD()) { - stockIndexString = getResources().getString(R.string.macd_normal); + stockIndexString = getString(ResourceTable.String_macd_normal); } else if (kLineLayout.isShownKDJ()) { - stockIndexString = getResources().getString(R.string.kdj_normal); + stockIndexString = getString(ResourceTable.String_kdj_normal); } else if (kLineLayout.isShownRSI()) { - stockIndexString = getResources().getString(R.string.rsi_normal); + stockIndexString = getString(ResourceTable.String_rsi_normal); } else if (kLineLayout.isShownBOLL()) { - stockIndexString = getResources().getString(R.string.boll_normal); + stockIndexString = getString(ResourceTable.String_boll_normal); } StockIndex_Text.setText(stockIndexString); } - @Override - public void onSingleTap(MotionEvent e, float x, float y) { - final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); - - if (kLineRender.getKLineRect().contains(x, y)) { - Toast.makeText(mActivity, "single tab [" + x + ", " + y + "]", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onDoubleTap(MotionEvent e, float x, float y) { - final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); - - if (kLineRender.getKLineRect().contains(x, y)) { - kLineRender.zoomIn(x, y); - } - } - @Override public void onLeftRefresh() { - Left_Loading_Image.setVisibility(View.VISIBLE); - ((Animatable) Left_Loading_Image.getDrawable()).start(); - // 模拟耗时 - kLineLayout.postDelayed(new Runnable() { + Left_Loading_Image.setVisibility(Component.VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Left_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { @Override public void run() { - Left_Loading_Image.setVisibility(View.GONE); - ((Animatable) Left_Loading_Image.getDrawable()).stop(); + Left_Loading_Image.setVisibility(Component.HIDE); List entries = insertEntries(); @@ -189,22 +182,24 @@ public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { kLineLayout.getKLineView().refreshComplete(entries.size() > 0); if (entries.size() == 0) { - Toast.makeText(mActivity, "已经到达最左边了", Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(getContext(), "已经到达最左边了"); } } - }, 1000); + }; + globalTaskDispatcher.asyncDispatch(runnable); } @Override public void onRightRefresh() { - Right_Loading_Image.setVisibility(View.VISIBLE); - ((Animatable) Right_Loading_Image.getDrawable()).start(); - // 模拟耗时 - kLineLayout.postDelayed(new Runnable() { + Right_Loading_Image.setVisibility(Component.VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Right_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { @Override public void run() { - Right_Loading_Image.setVisibility(View.GONE); - ((Animatable) Right_Loading_Image.getDrawable()).start(); + Right_Loading_Image.setVisibility(Component.HIDE); List entries = addEntries(); @@ -213,89 +208,65 @@ public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { kLineLayout.getKLineView().refreshComplete(entries.size() > 0); if (entries.size() == 0) { - Toast.makeText(mActivity, "已经到达最右边了", Toast.LENGTH_SHORT).show(); + ToastViewDialog.toast(getContext(), "已经到达最右边了"); } } - }, 1000); + }; + globalTaskDispatcher.asyncDispatch(runnable); } - }); - } - - private SpannableString getSpannableString(String str, int partColor0, int partColor1, int partColor2) { - String[] splitString = str.split("[●]"); - SpannableString spanString = new SpannableString(str); - - int pos0 = splitString[0].length(); - int pos1 = pos0 + splitString[1].length() + 1; - int end = str.length(); - - spanString.setSpan(new ForegroundColorSpan(partColor0), - pos0, pos1, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - - if (splitString.length > 2) { - int pos2 = pos1 + splitString[2].length() + 1; - - spanString.setSpan(new ForegroundColorSpan(partColor1), - pos1, pos2, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - spanString.setSpan(new ForegroundColorSpan(partColor2), - pos2, end, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - } else { - spanString.setSpan(new ForegroundColorSpan(partColor1), - pos1, end, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - } + @Override + public void onSingleTap(TouchEvent e, float x, float y) { + final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); + if (contains(kLineRender.getKLineRect(), x, y)) { + ToastViewDialog.toast(getContext(), "single tab [" + x + ", " + y + "]"); + } + } - return spanString; + @Override + public void onDoubleTap(TouchEvent e, float x, float y) { + final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); + if (contains(kLineRender.getKLineRect(), x, y)) { + kLineRender.zoomIn(x, y); + } + } + }); } private void loadKLineData() { - new AsyncTask() { + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { @Override - protected Void doInBackground(Void... params) { - + public void run() { PerformenceAnalyser.getInstance().addWatcher(); String kLineData = ""; + ResourceManager resourceManager = getContext().getResourceManager(); + RawFileEntry rawFileEntry = resourceManager.getRawFileEntry("resources/rawfile/kline1.txt"); + Resource resource = null; try { - InputStream in = getResources().getAssets().open("kline1.txt"); - int length = in.available(); - byte[] buffer = new byte[length]; - in.read(buffer); + resource = rawFileEntry.openRawFile(); + byte[] buffer = new byte[resource.available()]; + resource.read(buffer); kLineData = new String(buffer, "UTF-8"); - } catch (Exception e) { - e.printStackTrace(); + } catch (IOException e) { + String ee = e.toString(); } - PerformenceAnalyser.getInstance().addWatcher(); - entrySet = StockDataTest.parseKLineData(kLineData); - - PerformenceAnalyser.getInstance().addWatcher(); - entrySet.computeStockIndex(); - - PerformenceAnalyser.getInstance().addWatcher(); - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { kLineLayout.getKLineView().setEntrySet(loadFirst()); - - PerformenceAnalyser.getInstance().addWatcher(); - kLineLayout.getKLineView().notifyDataSetChanged(); - - PerformenceAnalyser.getInstance().addWatcher(); } - }.execute(); + }; + globalTaskDispatcher.asyncDispatch(runnable); } + private EntrySet loadFirst() { EntrySet set = new EntrySet(); - for (int i = loadStartPos ; i < loadEndPos ; i++) { + for (int i = loadStartPos; i < loadEndPos; i++) { set.addEntry(entrySet.getEntryList().get(i)); } return set; @@ -305,7 +276,7 @@ public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { List entries = new ArrayList<>(); int addCount = 0; - for (int i = loadEndPos ; i < loadEndPos + loadCount && i < entrySet.getEntryList().size() ; i++) { + for (int i = loadEndPos; i < loadEndPos + loadCount && i < entrySet.getEntryList().size(); i++) { addCount++; entries.add(entrySet.getEntryList().get(i)); @@ -319,7 +290,7 @@ public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { List entries = new ArrayList<>(); int insertCount = 0; - for (int i = loadStartPos ; i > loadStartPos - loadCount && i > -1 ; i--) { + for (int i = loadStartPos; i > loadStartPos - loadCount && i > -1; i--) { insertCount++; entries.add(entrySet.getEntryList().get(i)); @@ -329,8 +300,18 @@ public class Enable_Left_And_Right_Refresh_Activity extends BaseActivity { return entries; } - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, Enable_Left_And_Right_Refresh_Activity.class); - return intent; + private boolean contains(RectFloat rectFloat, float x, float y) { + return rectFloat.left < rectFloat.right && rectFloat.top < rectFloat.bottom + && x >= rectFloat.left && x < rectFloat.right && y >= rectFloat.top && y < rectFloat.bottom; + } + + @Override + public void onActive() { + super.onActive(); + } + + @Override + public void onForeground(Intent intent) { + super.onForeground(intent); } } diff --git a/app/src/main/java/com/wordplat/quickstart/activity/MACD_RSI_KDJ_Show_Together_Activity.java b/entry/src/main/java/com/wordplat/quickstart/slice/MACD_RSI_KDJ_Show_Together_Slice.java similarity index 49% rename from app/src/main/java/com/wordplat/quickstart/activity/MACD_RSI_KDJ_Show_Together_Activity.java rename to entry/src/main/java/com/wordplat/quickstart/slice/MACD_RSI_KDJ_Show_Together_Slice.java index 0204153986713d479bfb60f1a9ba8b485192dfe9..e54ae46c71f949a9536d426e1313d4a38f73a178 100644 --- a/app/src/main/java/com/wordplat/quickstart/activity/MACD_RSI_KDJ_Show_Together_Activity.java +++ b/entry/src/main/java/com/wordplat/quickstart/slice/MACD_RSI_KDJ_Show_Together_Slice.java @@ -1,64 +1,63 @@ -package com.wordplat.quickstart.activity; +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; +package com.wordplat.quickstart.slice; -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.utils.AppUtils; import com.wordplat.ikvstockchart.InteractiveKLineView; import com.wordplat.ikvstockchart.compat.PerformenceAnalyser; -import com.wordplat.ikvstockchart.drawing.HighlightDrawing; -import com.wordplat.ikvstockchart.drawing.KDJDrawing; -import com.wordplat.ikvstockchart.drawing.MACDDrawing; -import com.wordplat.ikvstockchart.drawing.RSIDrawing; -import com.wordplat.ikvstockchart.drawing.StockIndexYLabelDrawing; -import com.wordplat.ikvstockchart.entry.EntrySet; -import com.wordplat.ikvstockchart.entry.StockDataTest; -import com.wordplat.ikvstockchart.entry.StockKDJIndex; -import com.wordplat.ikvstockchart.entry.StockMACDIndex; -import com.wordplat.ikvstockchart.entry.StockRSIIndex; +import com.wordplat.ikvstockchart.compat.ViewUtils; +import com.wordplat.ikvstockchart.drawing.*; +import com.wordplat.ikvstockchart.entry.*; import com.wordplat.ikvstockchart.marker.XAxisTextMarkerView; import com.wordplat.ikvstockchart.marker.YAxisTextMarkerView; import com.wordplat.ikvstockchart.render.KLineRender; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.global.resource.ResourceManager; -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; +import java.io.IOException; -import java.io.InputStream; - -/** - *

MACD_RSI_KDJ_Show_Together_Activity

- *

Date: 2017/3/31

- * - * @author afon - */ - -@ContentView(R.layout.activity_macd_rsi_kdj_show_together) -public class MACD_RSI_KDJ_Show_Together_Activity extends BaseActivity { - - @ViewInject(R.id.kLineView) private InteractiveKLineView kLineView = null; +public class MACD_RSI_KDJ_Show_Together_Slice extends BaseAbilitySlice { + private InteractiveKLineView kLineView; private KLineRender kLineRender; @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - initUI(); + public void onStart(Intent intent) { + super.onStart(intent); + setUIContent(ResourceTable.Layout_slice_macd_rsi_kdj_show_together); + initViews(); loadKLineData(); } - private void initUI() { - kLineView.setEnableLeftRefresh(false); + private void initViews() { + kLineView = (InteractiveKLineView) findComponentById(ResourceTable.Id_kLineView_macd_rsi_kdj_show); + kLineView.setEnableLeftRefresh(false); kLineRender = (KLineRender) kLineView.getRender(); - final int paddingTop = AppUtils.dpTopx(mActivity, 10); - final int stockMarkerViewHeight = AppUtils.dpTopx(mActivity, 15); + final int paddingTop = ViewUtils.vpToPx(this, 10); + final int stockMarkerViewHeight = ViewUtils.vpToPx(this, 15); - // MACD + /** + * MACD + */ HighlightDrawing macdHighlightDrawing = new HighlightDrawing(); macdHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -69,7 +68,9 @@ public class MACD_RSI_KDJ_Show_Together_Activity extends BaseActivity { macdIndex.setPaddingTop(paddingTop); kLineRender.addStockIndex(macdIndex); - // RSI + /** + * RSI + */ HighlightDrawing rsiHighlightDrawing = new HighlightDrawing(); rsiHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -80,7 +81,9 @@ public class MACD_RSI_KDJ_Show_Together_Activity extends BaseActivity { rsiIndex.setPaddingTop(paddingTop); kLineRender.addStockIndex(rsiIndex); - // KDJ + /** + * KDJ + */ HighlightDrawing kdjHighlightDrawing = new HighlightDrawing(); kdjHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -96,54 +99,31 @@ public class MACD_RSI_KDJ_Show_Together_Activity extends BaseActivity { } private void loadKLineData() { - new AsyncTask() { - - private EntrySet entrySet; - + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { @Override - protected Void doInBackground(Void... params) { - + public void run() { + EntrySet entrySet; PerformenceAnalyser.getInstance().addWatcher(); - String kLineData = ""; + ResourceManager resourceManager = getContext().getResourceManager(); + RawFileEntry rawFileEntry = resourceManager.getRawFileEntry("resources/rawfile/kline1.txt"); + Resource resource = null; try { - InputStream in = getResources().getAssets().open("kline1.txt"); - int length = in.available(); - byte[] buffer = new byte[length]; - in.read(buffer); + resource = rawFileEntry.openRawFile(); + byte[] buffer = new byte[resource.available()]; + resource.read(buffer); kLineData = new String(buffer, "UTF-8"); - } catch (Exception e) { - e.printStackTrace(); + } catch (IOException e) { + String ss = e.toString(); } - PerformenceAnalyser.getInstance().addWatcher(); - entrySet = StockDataTest.parseKLineData(kLineData); - - PerformenceAnalyser.getInstance().addWatcher(); - entrySet.computeStockIndex(); - - PerformenceAnalyser.getInstance().addWatcher(); - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { kLineView.setEntrySet(entrySet); - - PerformenceAnalyser.getInstance().addWatcher(); - kLineView.notifyDataSetChanged(); - - PerformenceAnalyser.getInstance().addWatcher(); } - }.execute(); - } - - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, MACD_RSI_KDJ_Show_Together_Activity.class); - return intent; + }; + globalTaskDispatcher.delayDispatch(runnable,1000); } } diff --git a/entry/src/main/java/com/wordplat/quickstart/slice/MainSlice.java b/entry/src/main/java/com/wordplat/quickstart/slice/MainSlice.java new file mode 100644 index 0000000000000000000000000000000000000000..b0e74bb1fe769cdf933d6eaf80e383600e0a9b06 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/slice/MainSlice.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.wordplat.quickstart.slice; + +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.adapter.TextAdapter; +import com.wordplat.quickstart.base.BaseAbilitySlice; +import com.wordplat.quickstart.utils.ToastViewDialog; + +import ohos.aafwk.content.Intent; +import ohos.agp.components.ListContainer; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.KeyEvent; + +/** + * MainSlice + * + * @since 2021-05-08 + */ +public class MainSlice extends BaseAbilitySlice { + /** + * HiLogLabel + */ + static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MainAbilitySlice"); + private static final int NUM_16 = 16; + private static final int NUM_255 = 255; + private static final int NUM_12 = 12; + private static final int NUM_0 = 0; + private static final int NUM_2000 = 2000; + private ListContainer textList = null; + private TextAdapter textAdapter; + /** + * 点击两次退出 + */ + private long exitTime = 0L; + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_slice_main); + + initViews(); + } + + /** + * 初始化view + */ + private void initViews() { + textList = (ListContainer) findComponentById(ResourceTable.Id_textList); + + textAdapter = new TextAdapter(this); + textList.setItemProvider(textAdapter); + + for (int resultI = NUM_0; resultI < NUM_16; resultI++) { + String result = Integer.toHexString(NUM_255 - resultI * NUM_12); + HiLog.info(LABEL, "##d onCreate: " + result); + } + } + + @Override + public void onActive() { + super.onActive(); + } + + @Override + public void onForeground(Intent intent) { + super.onForeground(intent); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEY_BACK) { + exit(); + return false; + } + return super.onKeyDown(keyCode, event); + } + + private void exit() { + if ((System.currentTimeMillis() - exitTime) > NUM_2000) { + ToastViewDialog.toast(getApplicationContext(), "再按一次退出程序"); + exitTime = System.currentTimeMillis(); + } else { + this.exit(); + System.exit(0); + } + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/activity/Simple_TimeLine_Example_Activity.java b/entry/src/main/java/com/wordplat/quickstart/slice/Simple_TimeLine_Example_Slice.java similarity index 56% rename from app/src/main/java/com/wordplat/quickstart/activity/Simple_TimeLine_Example_Activity.java rename to entry/src/main/java/com/wordplat/quickstart/slice/Simple_TimeLine_Example_Slice.java index 0b73bab739ff98439108c02462acfc54360a8240..3d15af1262c6196834f61db754eb35f5d5f221fb 100644 --- a/app/src/main/java/com/wordplat/quickstart/activity/Simple_TimeLine_Example_Activity.java +++ b/entry/src/main/java/com/wordplat/quickstart/slice/Simple_TimeLine_Example_Slice.java @@ -1,86 +1,52 @@ -package com.wordplat.quickstart.activity; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; +package com.wordplat.quickstart.slice; import com.wordplat.ikvstockchart.InteractiveKLineView; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.render.TimeLineRender; -import com.wordplat.quickstart.R; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; import com.wordplat.quickstart.bean.BtcBean; import com.wordplat.quickstart.mvp.BtcChinaPresenter; import com.wordplat.quickstart.mvp.LoadingViewListener; -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; +import ohos.aafwk.content.Intent; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; /** - *

Simple_TimeLine_Example_Activity

- *

Date: 2017/4/16

+ * Simple_TimeLine_Example_Slice * - * @author afon + * @since 2021-04-30 */ - -@ContentView(R.layout.activity_simple_timeline) -public class Simple_TimeLine_Example_Activity extends BaseActivity { - - @ViewInject(R.id.timeLineView) private InteractiveKLineView timeLineView = null; - +public class Simple_TimeLine_Example_Slice extends BaseAbilitySlice { private static final int REQUEST_BTC_DATA = 1; - - private final EntrySet entrySet = new EntrySet(); - - private final BtcChinaPresenter presenter = new BtcChinaPresenter(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - initUI(); - } - - @Override - protected void onResume() { - super.onResume(); - - presenter.attachView(btcChinaListener); - presenter.getSimple(REQUEST_BTC_DATA); - } - - @Override - protected void onPause() { - super.onPause(); - - presenter.detachView(); - } - - private void initUI() { - timeLineView.setEntrySet(entrySet); - timeLineView.setRender(new TimeLineRender()); - } - + private static final int NUM_0 = 0; + private static final int NUM_2 = 2; + private static final int NUM_4 = 4; + private HiLogLabel label = new HiLogLabel(0, 0, "Simple_TimeLine_Example_Slice"); + private InteractiveKLineView timeLineView = null; private LoadingViewListener btcChinaListener = new LoadingViewListener() { @Override public void onStartRequest(int requestCode) { - + debug("onStartRequest"); } @Override public void onFinishRequest(int requestCode) { - + debug("onFinishRequest"); } @Override public void onSuccess(int requestCode) { + debug("onSuccess"); for (BtcBean btcBean : presenter.getBtcList()) { Entry entry = new Entry(btcBean.getPrice(), (int) btcBean.getAmount(), ""); entrySet.addEntry(entry); } - entrySet.getEntryList().get(0).setXLabel("09:30"); - entrySet.getEntryList().get(2).setXLabel("11:30/13:00"); - entrySet.getEntryList().get(4).setXLabel("15:00"); + entrySet.getEntryList().get(NUM_0).setXLabel("09:30"); + entrySet.getEntryList().get(NUM_2).setXLabel("11:30/13:00"); + entrySet.getEntryList().get(NUM_4).setXLabel("15:00"); timeLineView.notifyDataSetChanged(); } @@ -94,18 +60,52 @@ public class Simple_TimeLine_Example_Activity extends BaseActivity { @Override public void onNoNetworkError(int requestCode) { super.onNoNetworkError(requestCode); + debug("onNoNetworkError"); onResultEmpty(requestCode); } @Override public void onNetworkTimeOutError(int requestCode) { super.onNetworkTimeOutError(requestCode); + debug("onNetworkTimeOutError"); onResultEmpty(requestCode); } }; - public static Intent createIntent(Context context) { - Intent intent = new Intent(context, Simple_TimeLine_Example_Activity.class); - return intent; + private final EntrySet entrySet = new EntrySet(); + + private final BtcChinaPresenter presenter = new BtcChinaPresenter(); + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_slice_simple_timeline); + initViews(); + } + + /** + * 初始化View + */ + private void initViews() { + timeLineView = (InteractiveKLineView) findComponentById(ResourceTable.Id_timeLineView); + timeLineView.setEntrySet(entrySet); + timeLineView.setRender(new TimeLineRender()); + } + + @Override + protected void onActive() { + super.onActive(); + presenter.attachView(btcChinaListener); + presenter.getSimple(REQUEST_BTC_DATA); + } + + @Override + protected void onBackground() { + super.onBackground(); + presenter.detachView(); + } + + private void debug(String msg) { + HiLog.error(label, msg); } } diff --git a/entry/src/main/java/com/wordplat/quickstart/slice/With_Fragment_And_TabLayout_Switcher_Example_Slice.java b/entry/src/main/java/com/wordplat/quickstart/slice/With_Fragment_And_TabLayout_Switcher_Example_Slice.java new file mode 100644 index 0000000000000000000000000000000000000000..fcf2e27b8cfa6ffa35345b3cc023ad47449e738f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/slice/With_Fragment_And_TabLayout_Switcher_Example_Slice.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.wordplat.quickstart.slice; + +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; +import com.wordplat.quickstart.mvp.StockPresenter; +import com.wordplat.quickstart.slider.PagerProvider; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.RefreshComponent; +import com.wordplat.quickstart.wight.navigation.BottomNavigation; +import com.wordplat.quickstart.wight.navigation.BottomNavigationItem; +import ohos.aafwk.content.Intent; +import ohos.agp.components.PageSlider; + +import java.util.ArrayList; + +/** + * With_Fragment_And_TabLayout_Switcher_Example_Slice + * + * @since 2021-04-22 + */ +public class With_Fragment_And_TabLayout_Switcher_Example_Slice extends BaseAbilitySlice { + private BottomNavigation mTabLayout; + private RefreshComponent refreshComponent; + private ArrayList bottomNavigationItems = new ArrayList<>(); + private PageSlider mkLineContent; + private PagerProvider dayKLinePager; + private PagerProvider weekKLinePager; + private PagerProvider monthKLinePager; + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + setUIContent(ResourceTable.Layout_slice_with_fragment_and_tablayout_switcher); + initViews(); + refresh(); + } + + private void initViews() { + mTabLayout = (BottomNavigation) findComponentById(ResourceTable.Id_tabLayout); + mkLineContent = (PageSlider) findComponentById(ResourceTable.Id_kLineContent); + refreshComponent = (RefreshComponent) findComponentById(ResourceTable.Id_fragmentRefreshComponent); + + dayKLinePager = PagerProvider.newInstance(this, StockPresenter.KLineType.DAY, refreshComponent); + weekKLinePager = PagerProvider.newInstance(this, StockPresenter.KLineType.WEEK, refreshComponent); + monthKLinePager = PagerProvider.newInstance(this, StockPresenter.KLineType.MONTH, refreshComponent); + mkLineContent.setProvider(dayKLinePager); + mkLineContent.setProvider(weekKLinePager); + mkLineContent.setProvider(monthKLinePager); + mkLineContent.setCurrentPage(0); + mkLineContent.setSlidingPossible(false); + + dayKLinePager = new PagerProvider(this, StockPresenter.KLineType.DAY); + + mkLineContent.addPageChangedListener(new PageSlider.PageChangedListener() { + @Override + public void onPageSliding(int i, float v, int i1) { + } + + @Override + public void onPageSlideStateChanged(int i) { + } + + @Override + public void onPageChosen(int i) { + } + }); + } + + /** + * refresh + */ + public void refresh() { + mTabLayout.removeAllItems(); + mTabLayout.setColored(false); + mTabLayout.setSelectedBackgroundVisible(false); + bottomNavigationItems.clear(); + + BottomNavigationItem item1 = + new BottomNavigationItem( + "日K", getContext()); + BottomNavigationItem item2 = + new BottomNavigationItem( + "周K", getContext()); + BottomNavigationItem item3 = + new BottomNavigationItem( + "月K", getContext()); + + bottomNavigationItems.add(item1); + bottomNavigationItems.add(item2); + bottomNavigationItems.add(item3); + mTabLayout.setSelectHideNotification(false); + mTabLayout.addItems(bottomNavigationItems); + + setListener(); + } + + private void setListener() { + mTabLayout.setOnTabSelectedListener( + new BottomNavigation.OnTabSelectedListener() { + @Override + public boolean onTabSelected(int position, boolean wasSelected) { + mkLineContent.setCurrentPage(position); + switch (position) { + case 0: + dayKLinePager.loadData(); + break; + case 1: + weekKLinePager.loadData(); + break; + case 2: + monthKLinePager.loadData(); + break; + default: + break; + } + return true; + } + }); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/slice/With_Pull_To_Refresh_Example_Slice.java b/entry/src/main/java/com/wordplat/quickstart/slice/With_Pull_To_Refresh_Example_Slice.java new file mode 100644 index 0000000000000000000000000000000000000000..73ed01fd4d94f48dbebd36d20df98583a9dafc24 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/slice/With_Pull_To_Refresh_Example_Slice.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.slice; + +import com.wordplat.ikvstockchart.InteractiveKLineLayout; +import com.wordplat.ikvstockchart.InteractiveKLineView; +import com.wordplat.ikvstockchart.KLineHandler; +import com.wordplat.ikvstockchart.compat.PerformenceAnalyser; +import com.wordplat.ikvstockchart.entry.Entry; +import com.wordplat.ikvstockchart.entry.EntrySet; +import com.wordplat.ikvstockchart.entry.SizeColor; +import com.wordplat.ikvstockchart.entry.StockDataTest; +import com.wordplat.ikvstockchart.render.KLineRender; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; +import com.wordplat.quickstart.utils.ToastViewDialog; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.HeadDefaultComponent; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.IRefresh; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.OnRefreshComponent; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.RefreshComponent; + +import ohos.aafwk.content.Intent; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.agp.utils.RectFloat; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.global.resource.ResourceManager; +import ohos.multimodalinput.event.TouchEvent; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * With_Pull_To_Refresh_Example_Slice + * + * @since 2021-04-30 + */ +public class With_Pull_To_Refresh_Example_Slice extends BaseAbilitySlice { + private RefreshComponent refreshComponent; + private InteractiveKLineLayout kLineLayout; + private Text MA_Text; + private Text StockIndex_Text; + private Text Volume_Text; + private Image Left_Loading_Image; + private Image Right_Loading_Image; + private RadioContainer But_Group; + private RadioButton MACD_But; + private RadioButton RSI_But; + private RadioButton KDJ_But; + private RadioButton BOLL_But; + + private EntrySet entrySet; + private int loadStartPos = 5500; + private int loadEndPos = 6000; + private int loadCount = 100; + + /** + * 动画效果 mAnimator + */ + private AnimatorProperty mAnimator; + /** + * viewOnTouchInterface + */ + public InteractiveKLineView.ViewOnTouchInterface viewOnTouchInterface; + + /** + * 定义回调方法 + * + * @param onRefreshComponent + */ + private OnRefreshComponent onRefreshComponent; + + public void setOnRefresh(OnRefreshComponent onRefreshComponent) { + this.onRefreshComponent = onRefreshComponent; + } + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + setUIContent(ResourceTable.Layout_slice_with_pull_to_refresh); + initViews(); + loadKLineData(); + } + + private void initViews() { + refreshComponent = (RefreshComponent) findComponentById(ResourceTable.Id_pullRefreshComponent); + kLineLayout = (InteractiveKLineLayout) findComponentById(ResourceTable.Id_kLineLayout); + MA_Text = (Text) findComponentById(ResourceTable.Id_MA_Text); + StockIndex_Text = (Text) findComponentById(ResourceTable.Id_StockIndex_Text); + Volume_Text = (Text) findComponentById(ResourceTable.Id_Volume_Text); + Left_Loading_Image = (Image) findComponentById(ResourceTable.Id_Left_Loading_Image); + Right_Loading_Image = (Image) findComponentById(ResourceTable.Id_Right_Loading_Image); + + But_Group = (RadioContainer) findComponentById(ResourceTable.Id_But_Group); + MACD_But = (RadioButton) findComponentById(ResourceTable.Id_MACD_But); + RSI_But = (RadioButton) findComponentById(ResourceTable.Id_RSI_But); + KDJ_But = (RadioButton) findComponentById(ResourceTable.Id_KDJ_But); + BOLL_But = (RadioButton) findComponentById(ResourceTable.Id_BOLL_But); + + refreshComponent.setKLineView(kLineLayout.getKLineView()); + /** + * 设置头部刷新样式,可自定义样式 + */ + HeadDefaultComponent headDefaultComponent = new HeadDefaultComponent(this); + /** + * 添加样式到头部 + */ + refreshComponent.setHeadComponent(headDefaultComponent); + + /** + * 设置刷新回调 + */ + refreshComponent.setRefreshListener(new IRefresh.RefreshListener() { + @Override + public void onRefresh() { + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + // 数据更新完 结束掉刷新 + refreshComponent.refreshFinish(); + } + }; + globalTaskDispatcher.delayDispatch(runnable,2000); + } + + @Override + public boolean enableRefresh() { + return false; + } + }); + kLineLayout.setClick(But_Group, MACD_But, RSI_But, KDJ_But, BOLL_But); + kLineLayout.setKLineHandler(new KLineHandler() { + @Override + public void onHighlight(Entry entry, int entryIndex, float x, float y) { + final SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); + String maString = null; + String volumeString = null; + String str = null; + maString = String.format(getString(ResourceTable.String_ma_highlight), + entry.getMa5(), + entry.getMa10(), + entry.getMa20()); + volumeString = String.format(getString(ResourceTable.String_volume_highlight), + entry.getVolumeMa5(), + entry.getVolumeMa10()); + + if (kLineLayout.isShownMACD()) { + str = String.format(getString(ResourceTable.String_macd_highlight), + entry.getDiff(), + entry.getDea(), + entry.getMacd()); + } else if (kLineLayout.isShownKDJ()) { + str = String.format(getString(ResourceTable.String_kdj_highlight), + entry.getK(), + entry.getD(), + entry.getJ()); + } else if (kLineLayout.isShownRSI()) { + + str = String.format(getString(ResourceTable.String_rsi_highlight), + entry.getRsi1(), + entry.getRsi2(), + entry.getRsi3()); + } else if (kLineLayout.isShownBOLL()) { + str = String.format(getString(ResourceTable.String_boll_highlight), + entry.getMb(), + entry.getUp(), + entry.getDn()); + } + + + MA_Text.setText(maString); + Volume_Text.setText(volumeString); + StockIndex_Text.setText(str); + } + + @Override + public void onCancelHighlight() { + String maString = getString(ResourceTable.String_ma_normal); + MA_Text.setText(maString); + Volume_Text.setText(""); + + String stockIndexString = ""; + if (kLineLayout.isShownMACD()) { + stockIndexString = getString(ResourceTable.String_macd_normal); + } else if (kLineLayout.isShownKDJ()) { + stockIndexString = getString(ResourceTable.String_kdj_normal); + } else if (kLineLayout.isShownRSI()) { + stockIndexString = getString(ResourceTable.String_rsi_normal); + } else if (kLineLayout.isShownBOLL()) { + stockIndexString = getString(ResourceTable.String_boll_normal); + } + StockIndex_Text.setText(stockIndexString); + } + + @Override + public void onLeftRefresh() { + Left_Loading_Image.setVisibility(Component.VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Left_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + Left_Loading_Image.setVisibility(Component.HIDE); + + List entries = insertEntries(); + + kLineLayout.getKLineView().getRender().getEntrySet().insertFirst(entries); + kLineLayout.getKLineView().notifyDataSetChanged(); + kLineLayout.getKLineView().refreshComplete(entries.size() > 0); + + if (entries.size() == 0) { + ToastViewDialog.toast(getContext(), "已经到达最左边了"); + } + } + }; + globalTaskDispatcher.delayDispatch(runnable, 1000); + } + + @Override + public void onRightRefresh() { + Right_Loading_Image.setVisibility(Component.VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Left_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + Right_Loading_Image.setVisibility(Component.HIDE); + + List entries = addEntries(); + + kLineLayout.getKLineView().getRender().getEntrySet().addEntries(entries); + kLineLayout.getKLineView().notifyDataSetChanged(); + kLineLayout.getKLineView().refreshComplete(entries.size() > 0); + + if (entries.size() == 0) { + ToastViewDialog.toast(getContext(), "已经到达最右边了"); + } + } + }; + globalTaskDispatcher.delayDispatch(runnable, 1000); + } + + @Override + public void onSingleTap(TouchEvent e, float x, float y) { + final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); + if (contains(kLineRender.getKLineRect(), x, y)) { + ToastViewDialog.toast(getContext(), "single tab [" + x + ", " + y + "]"); + } + } + + @Override + public void onDoubleTap(TouchEvent e, float x, float y) { + final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); + if (contains(kLineRender.getKLineRect(), x, y)) { + kLineRender.zoomIn(x, y); + } + } + }); + } + + private void loadKLineData() { + TaskDispatcher globalTaskDispatcher = getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + PerformenceAnalyser.getInstance().addWatcher(); + + String kLineData = ""; + ResourceManager resourceManager = getContext().getResourceManager(); + RawFileEntry rawFileEntry = resourceManager.getRawFileEntry("resources/rawfile/kline1.txt"); + Resource resource = null; + try { + resource = rawFileEntry.openRawFile(); + byte[] buffer = new byte[resource.available()]; + resource.read(buffer); + kLineData = new String(buffer, "UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + } + + entrySet = StockDataTest.parseKLineData(kLineData); + entrySet.computeStockIndex(); + kLineLayout.getKLineView().setEntrySet(loadFirst()); + kLineLayout.getKLineView().notifyDataSetChanged(); + } + }; + globalTaskDispatcher.asyncDispatch(runnable); + } + + private EntrySet loadFirst() { + EntrySet set = new EntrySet(); + + for (int i = loadStartPos; i < loadEndPos; i++) { + set.addEntry(entrySet.getEntryList().get(i)); + } + return set; + } + + private List addEntries() { + List entries = new ArrayList<>(); + + int addCount = 0; + for (int i = loadEndPos; i < loadEndPos + loadCount && i < entrySet.getEntryList().size(); i++) { + addCount++; + + entries.add(entrySet.getEntryList().get(i)); + } + loadEndPos += addCount; + + return entries; + } + + private List insertEntries() { + List entries = new ArrayList<>(); + + int insertCount = 0; + for (int i = loadStartPos; i > loadStartPos - loadCount && i > -1; i--) { + insertCount++; + + entries.add(entrySet.getEntryList().get(i)); + } + loadStartPos -= insertCount; + + return entries; + } + + private boolean contains(RectFloat rectFloat, float x, float y) { + return rectFloat.left < rectFloat.right && rectFloat.top < rectFloat.bottom + && x >= rectFloat.left && x < rectFloat.right && y >= rectFloat.top && y < rectFloat.bottom; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/slice/With_RecyclerView_Example_Slice.java b/entry/src/main/java/com/wordplat/quickstart/slice/With_RecyclerView_Example_Slice.java new file mode 100644 index 0000000000000000000000000000000000000000..bf8bebcfd18d0fb953ff705c672538f290ed3e12 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/slice/With_RecyclerView_Example_Slice.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.slice; + +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.base.BaseAbilitySlice; + +import ohos.aafwk.content.Intent; + +/** + * With_Pull_To_Refresh_Example_Slice + * + * @since 2021-04-30 + */ +public class With_RecyclerView_Example_Slice extends BaseAbilitySlice { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + setUIContent(ResourceTable.Layout_slice_with_pull_to_refresh); + initViews(); + } + + private void initViews() { + } +} diff --git a/app/src/main/java/com/wordplat/quickstart/fragment/KLineFragment.java b/entry/src/main/java/com/wordplat/quickstart/slider/PagerProvider.java similarity index 38% rename from app/src/main/java/com/wordplat/quickstart/fragment/KLineFragment.java rename to entry/src/main/java/com/wordplat/quickstart/slider/PagerProvider.java index 1d768fdf1a5700b231c81109f51dba453cc60749..dafaacd2c0641ff9b5759aa2c91b66ae1b7251d9 100644 --- a/app/src/main/java/com/wordplat/quickstart/fragment/KLineFragment.java +++ b/entry/src/main/java/com/wordplat/quickstart/slider/PagerProvider.java @@ -1,246 +1,103 @@ -package com.wordplat.quickstart.fragment; - -import android.graphics.drawable.Animatable; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.wordplat.quickstart.R; -import com.wordplat.quickstart.bean.KLineBean; -import com.wordplat.quickstart.mvp.LoadingViewListener; -import com.wordplat.quickstart.mvp.StockPresenter; +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.slider; + import com.wordplat.ikvstockchart.InteractiveKLineLayout; import com.wordplat.ikvstockchart.KLineHandler; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.KLineRender; - -import org.xutils.view.annotation.ContentView; -import org.xutils.view.annotation.ViewInject; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.bean.KLineBean; +import com.wordplat.quickstart.mvp.LoadingViewListener; +import com.wordplat.quickstart.mvp.StockPresenter; +import com.wordplat.quickstart.utils.ToastViewDialog; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.HeadDefaultComponent; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.IRefresh; +import com.wordplat.quickstart.wight.PullLayout.pulllistview.RefreshComponent; + +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.agp.utils.RectFloat; +import ohos.app.Context; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.multimodalinput.event.TouchEvent; import java.util.List; +import static ohos.agp.components.Component.VISIBLE; + /** - *

KLineFragment

- *

Date: 2017/4/5

+ * PagerProvider * - * @author afon + * @since 2021-04-22 */ - -@ContentView(R.layout.fragment_kline) -public class KLineFragment extends BaseFragment { - private static final String TAG = "KLineFragment"; - - @ViewInject(R.id.kLineLayout) private InteractiveKLineLayout kLineLayout = null; - @ViewInject(R.id.MA_Text) private TextView MA_Text = null; - @ViewInject(R.id.StockIndex_Text) private TextView StockIndex_Text = null; - @ViewInject(R.id.Volume_Text) private TextView Volume_Text = null; - @ViewInject(R.id.Left_Loading_Image) private ImageView Left_Loading_Image = null; - @ViewInject(R.id.Right_Loading_Image) private ImageView Right_Loading_Image = null; - - private static final String STOCK_CODE = "600030"; // 中信证券 +public class PagerProvider extends PageSliderProvider { + /** + * 中信证券 + */ + private static final String STOCK_CODE = "600030"; private static final int REQUEST_STOCK_FIRST = 1; private static final int REQUEST_STOCK_PREV = 2; private static final int REQUEST_STOCK_NEXT = 3; + private static RefreshComponent refreshComponent; + + private final Context mContext; + private InteractiveKLineLayout kLineLayout = null; + private Text MA_Text = null; + private Text StockIndex_Text = null; + private Text Volume_Text = null; + private Image Left_Loading_Image; + private Image Right_Loading_Image; + + private RadioContainer But_Group; + private RadioButton MACD_But; + private RadioButton RSI_But; + private RadioButton KDJ_But; + private RadioButton BOLL_But; + + private int loadStartPos = 5500; + private int loadEndPos = 6000; + private int loadCount = 100; private StockPresenter.KLineType kLineType; - - private final EntrySet entrySet = new EntrySet(); - + private EntrySet entrySet = new EntrySet(); private final StockPresenter presenter = new StockPresenter(); - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - initUI(); - } - - @Override - public void onResume() { - super.onResume(); - - presenter.attachView(viewListener); - presenter.loadFirst(REQUEST_STOCK_FIRST, STOCK_CODE, kLineType); - } - - @Override - public void onPause() { - super.onPause(); - - presenter.detachView(); - } - - private void initUI() { - kLineType = (StockPresenter.KLineType) getArguments().getSerializable(A); - - kLineLayout.setKLineHandler(new KLineHandler() { - @Override - public void onHighlight(Entry entry, int entryIndex, float x, float y) { - final SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); - - String maString = String.format(getResources().getString(R.string.ma_highlight), - entry.getMa5(), - entry.getMa10(), - entry.getMa20()); - - MA_Text.setText(getSpannableString(maString, - sizeColor.getMa5Color(), - sizeColor.getMa10Color(), - sizeColor.getMa20Color())); - - String volumeString = String.format(getResources().getString(R.string.volume_highlight), - entry.getVolumeMa5(), - entry.getVolumeMa10()); - - Volume_Text.setText(getSpannableString(volumeString, - sizeColor.getMa5Color(), - sizeColor.getMa10Color(), - 0)); - - SpannableString spanString = new SpannableString(""); - if (kLineLayout.isShownMACD()) { - String str = String.format(getResources().getString(R.string.macd_highlight), - entry.getDiff(), - entry.getDea(), - entry.getMacd()); - - spanString = getSpannableString(str, - sizeColor.getDiffLineColor(), - sizeColor.getDeaLineColor(), - sizeColor.getMacdHighlightTextColor()); - - } else if (kLineLayout.isShownKDJ()) { - String str = String.format(getResources().getString(R.string.kdj_highlight), - entry.getK(), - entry.getD(), - entry.getJ()); - - spanString = getSpannableString(str, - sizeColor.getKdjKLineColor(), - sizeColor.getKdjDLineColor(), - sizeColor.getKdjJLineColor()); - - } else if (kLineLayout.isShownRSI()) { - String str = String.format(getResources().getString(R.string.rsi_highlight), - entry.getRsi1(), - entry.getRsi2(), - entry.getRsi3()); - - spanString = getSpannableString(str, - sizeColor.getRsi1LineColor(), - sizeColor.getRsi2LineColor(), - sizeColor.getRsi3LineColor()); - - } else if (kLineLayout.isShownBOLL()) { - String str = String.format(getResources().getString(R.string.boll_highlight), - entry.getMb(), - entry.getUp(), - entry.getDn()); - - spanString = getSpannableString(str, - sizeColor.getBollMidLineColor(), - sizeColor.getBollUpperLineColor(), - sizeColor.getBollLowerLineColor()); - } - StockIndex_Text.setText(spanString); - } - - @Override - public void onCancelHighlight() { - String maString = getResources().getString(R.string.ma_normal); - MA_Text.setText(maString); - Volume_Text.setText(""); - - String stockIndexString = ""; - if (kLineLayout.isShownMACD()) { - stockIndexString = getResources().getString(R.string.macd_normal); - } else if (kLineLayout.isShownKDJ()) { - stockIndexString = getResources().getString(R.string.kdj_normal); - } else if (kLineLayout.isShownRSI()) { - stockIndexString = getResources().getString(R.string.rsi_normal); - } else if (kLineLayout.isShownBOLL()) { - stockIndexString = getResources().getString(R.string.boll_normal); - } - StockIndex_Text.setText(stockIndexString); - } - - @Override - public void onSingleTap(MotionEvent e, float x, float y) { - final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); - - if (kLineRender.getKLineRect().contains(x, y)) { - Toast.makeText(mActivity, "single tab [" + x + ", " + y + "]", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onDoubleTap(MotionEvent e, float x, float y) { - final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); - - if (kLineRender.getKLineRect().contains(x, y)) { - kLineRender.zoomIn(x, y); - } - } - - @Override - public void onLeftRefresh() { - presenter.loadPrev(REQUEST_STOCK_PREV, STOCK_CODE, kLineType); - } - - @Override - public void onRightRefresh() { - presenter.loadNext(REQUEST_STOCK_NEXT, STOCK_CODE, kLineType); - } - }); - } - - private SpannableString getSpannableString(String str, int partColor0, int partColor1, int partColor2) { - String[] splitString = str.split("[●]"); - SpannableString spanString = new SpannableString(str); - - int pos0 = splitString[0].length(); - int pos1 = pos0 + splitString[1].length() + 1; - int end = str.length(); - - spanString.setSpan(new ForegroundColorSpan(partColor0), - pos0, pos1, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - - if (splitString.length > 2) { - int pos2 = pos1 + splitString[2].length() + 1; - - spanString.setSpan(new ForegroundColorSpan(partColor1), - pos1, pos2, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - - spanString.setSpan(new ForegroundColorSpan(partColor2), - pos2, end, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - } else { - spanString.setSpan(new ForegroundColorSpan(partColor1), - pos1, end, SpannableString.SPAN_EXCLUSIVE_INCLUSIVE); - } - - return spanString; - } + private AnimatorProperty mAnimator; private LoadingViewListener viewListener = new LoadingViewListener() { @Override public void onStartRequest(int requestCode) { switch (requestCode) { case REQUEST_STOCK_PREV: - Left_Loading_Image.setVisibility(View.VISIBLE); - ((Animatable) Left_Loading_Image.getDrawable()).start(); + Left_Loading_Image.setVisibility(VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Left_Loading_Image).start(); break; case REQUEST_STOCK_NEXT: - Right_Loading_Image.setVisibility(View.VISIBLE); - ((Animatable) Right_Loading_Image.getDrawable()).start(); + Right_Loading_Image.setVisibility(VISIBLE); + mAnimator = Right_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Right_Loading_Image).start(); break; } } @@ -249,13 +106,26 @@ public class KLineFragment extends BaseFragment { public void onFinishRequest(int requestCode) { switch (requestCode) { case REQUEST_STOCK_PREV: - Left_Loading_Image.setVisibility(View.GONE); - ((Animatable) Left_Loading_Image.getDrawable()).stop(); + TaskDispatcher globalTaskDispatcher = mContext.getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + Left_Loading_Image.setVisibility(Component.HIDE); + ToastViewDialog.toast(mContext, "已经到达最左边了"); + } + }; + globalTaskDispatcher.delayDispatch(runnable, 1500); break; - case REQUEST_STOCK_NEXT: - Right_Loading_Image.setVisibility(View.GONE); - ((Animatable) Right_Loading_Image.getDrawable()).start(); + TaskDispatcher globalTaskDispatcher2 = mContext.getMainTaskDispatcher(); + Runnable runnable2 = new Runnable() { + @Override + public void run() { + Right_Loading_Image.setVisibility(Component.HIDE); + ToastViewDialog.toast(mContext, "已经到达最右边了"); + } + }; + globalTaskDispatcher2.delayDispatch(runnable2, 1500); break; } } @@ -276,11 +146,10 @@ public class KLineFragment extends BaseFragment { } entrySet.computeStockIndex(); kLineLayout.getKLineView().setEntrySet(entrySet); - kLineLayout.getKLineView().notifyDataSetChanged(); break; case REQUEST_STOCK_PREV: - for (int i = response.size() - 1 ; i >= 0 ; i--) { + for (int i = response.size() - 1; i >= 0; i--) { KLineBean kLineBean = response.get(i); entrySet.insertFirst(new Entry(kLineBean.getOpen(), kLineBean.getHigh(), @@ -290,7 +159,6 @@ public class KLineFragment extends BaseFragment { kLineBean.getDate())); } entrySet.computeStockIndex(); - kLineLayout.getKLineView().notifyDataSetChanged(); kLineLayout.getKLineView().refreshComplete(response.size() > 0); break; @@ -304,7 +172,6 @@ public class KLineFragment extends BaseFragment { kLineBean.getDate())); } entrySet.computeStockIndex(); - kLineLayout.getKLineView().notifyDataSetChanged(); kLineLayout.getKLineView().refreshComplete(response.size() > 0); break; } @@ -314,18 +181,16 @@ public class KLineFragment extends BaseFragment { public void onResultEmpty(int requestCode) { switch (requestCode) { case REQUEST_STOCK_FIRST: - entrySet.setLoadingStatus(false); - kLineLayout.getKLineView().notifyDataSetChanged(); break; case REQUEST_STOCK_PREV: kLineLayout.getKLineView().refreshComplete(false); - Toast.makeText(mActivity, "已经到达最左边了", Toast.LENGTH_SHORT).show(); + break; case REQUEST_STOCK_NEXT: kLineLayout.getKLineView().refreshComplete(false); - Toast.makeText(mActivity, "已经到达最右边了", Toast.LENGTH_SHORT).show(); + break; } } @@ -337,7 +202,6 @@ public class KLineFragment extends BaseFragment { switch (requestCode) { case REQUEST_STOCK_FIRST: entrySet.setLoadingStatus(false); - kLineLayout.getKLineView().notifyDataSetChanged(); break; } } @@ -349,20 +213,236 @@ public class KLineFragment extends BaseFragment { switch (requestCode) { case REQUEST_STOCK_FIRST: entrySet.setLoadingStatus(false); - kLineLayout.getKLineView().notifyDataSetChanged(); break; } } }; - private static final String A = "a"; + /** + * PagerProvider + * + * @param context + * @param type + */ + public PagerProvider(Context context, StockPresenter.KLineType type) { + this.mContext = context; + this.kLineType = type; + } + + @Override + public int getCount() { + return 1; + } + + @Override + public Object createPageInContainer(ComponentContainer componentContainer, int i) { + DirectionalLayout layout = (DirectionalLayout) LayoutScatter.getInstance(mContext).parse( + ResourceTable.Layout_slider_kline, null, false); + initViews(layout); + initUI(); + componentContainer.addComponent(layout); + setRefresh(); + presenter.attachView(viewListener); + presenter.loadFirst(REQUEST_STOCK_FIRST, STOCK_CODE, kLineType); + return layout; + } + + private void setRefresh() { + refreshComponent.setKLineView(kLineLayout.getKLineView()); + /** + * 设置头部刷新样式,可自定义样式 + */ + HeadDefaultComponent headDefaultComponent = new HeadDefaultComponent(refreshComponent.getContext()); + /** + * 添加样式到头部 + */ + refreshComponent.setHeadComponent(headDefaultComponent); + /** + * 设置刷新回调 + */ + refreshComponent.setRefreshListener(new IRefresh.RefreshListener() { + @Override + public void onRefresh() { + new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() { + @Override + public void run() { + /** + * 数据更新完 结束掉刷新 + */ + refreshComponent.refreshFinish(); + } + }, 2000); + } + + @Override + public boolean enableRefresh() { + return false; + } + }); + } + + private void initUI() { + kLineLayout.setClick(But_Group, MACD_But, RSI_But, KDJ_But, BOLL_But); + kLineLayout.getKLineView().isEmpty(true); + kLineLayout.setKLineHandler(new KLineHandler() { + @Override + public void onHighlight(Entry entry, int entryIndex, float x, float y) { + final SizeColor sizeColor = kLineLayout.getKLineView().getRender().getSizeColor(); + String maString = null; + String volumeString = null; + String str = null; + maString = String.format(String.valueOf(mContext.getString(ResourceTable.String_ma_highlight)), + entry.getMa5(), + entry.getMa10(), + entry.getMa20()); + volumeString = String.format(String.valueOf(mContext.getString(ResourceTable.String_volume_highlight)), + entry.getVolumeMa5(), + entry.getVolumeMa10()); + if (kLineLayout.isShownMACD()) { + str = String.format(String.valueOf(mContext.getString(ResourceTable.String_macd_highlight)), + entry.getDiff(), + entry.getDea(), + entry.getMacd()); + } else if (kLineLayout.isShownKDJ()) { + str = String.format(String.valueOf(mContext.getString(ResourceTable.String_kdj_highlight)), + entry.getK(), + entry.getD(), + entry.getJ()); + } else if (kLineLayout.isShownRSI()) { + str = String.format(String.valueOf(mContext.getString(ResourceTable.String_rsi_highlight)), + entry.getRsi1(), + entry.getRsi2(), + entry.getRsi3()); + } else if (kLineLayout.isShownBOLL()) { + str = String.format(String.valueOf(mContext.getString(ResourceTable.String_boll_highlight)), + entry.getMb(), + entry.getUp(), + entry.getDn()); + } + + MA_Text.setText(maString); + Volume_Text.setText(volumeString); + StockIndex_Text.setText(str); + } + + @Override + public void onCancelHighlight() { + String maString = null; + String stockIndexString = ""; + maString = String.valueOf(mContext.getString(ResourceTable.String_ma_normal)); + if (kLineLayout.isShownMACD()) { + stockIndexString = String.valueOf(mContext.getString(ResourceTable.String_macd_normal)); + } else if (kLineLayout.isShownKDJ()) { + stockIndexString = String.valueOf(mContext.getString(ResourceTable.String_kdj_normal)); + } else if (kLineLayout.isShownRSI()) { + stockIndexString = String.valueOf(mContext.getString(ResourceTable.String_rsi_normal)); + } else if (kLineLayout.isShownBOLL()) { + stockIndexString = String.valueOf(mContext.getString(ResourceTable.String_boll_normal)); + } + + MA_Text.setText(maString); + Volume_Text.setText(""); + StockIndex_Text.setText(stockIndexString); + } + + @Override + public void onLeftRefresh() { + Left_Loading_Image.setVisibility(VISIBLE); + mAnimator = Left_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Left_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = mContext.getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + + public void run() { + Left_Loading_Image.setVisibility(Component.HIDE); + ToastViewDialog.toast(mContext, "已经到达最左边了"); + } + }; + globalTaskDispatcher.delayDispatch(runnable, 1000); + } - public static KLineFragment newInstance(StockPresenter.KLineType kLineType) { - Bundle args = new Bundle(); - args.putSerializable(A, kLineType); + @Override + public void onRightRefresh() { + Right_Loading_Image.setVisibility(VISIBLE); + mAnimator = Right_Loading_Image.createAnimatorProperty(); + mAnimator.setDuration(1500).rotate(360).setLoopedCount(AnimatorProperty.INFINITE) + .setTarget(Right_Loading_Image).start(); + TaskDispatcher globalTaskDispatcher = mContext.getMainTaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + Right_Loading_Image.setVisibility(Component.HIDE); + ToastViewDialog.toast(mContext, "已经到达最右边了"); + } + }; + globalTaskDispatcher.delayDispatch(runnable, 1000); + } + + @Override + public void onSingleTap(TouchEvent e, float x, float y) { + ToastViewDialog.toast(mContext, "single tab [" + x + ", " + y + "]"); + } + + @Override + public void onDoubleTap(TouchEvent e, float x, float y) { + final KLineRender kLineRender = (KLineRender) kLineLayout.getKLineView().getRender(); + if (contains(kLineRender.getKLineRect(), x, y)) { + kLineRender.zoomIn(x, y); + } + } + }); + } + + private void initViews(DirectionalLayout layout) { + kLineLayout = (InteractiveKLineLayout) layout.findComponentById(ResourceTable.Id_kLineLayout); + MA_Text = (Text) layout.findComponentById(ResourceTable.Id_MA_Text); + StockIndex_Text = (Text) layout.findComponentById(ResourceTable.Id_StockIndex_Text); + Volume_Text = (Text) layout.findComponentById(ResourceTable.Id_Volume_Text); + Left_Loading_Image = (Image) layout.findComponentById(ResourceTable.Id_Left_Loading_Image); + Right_Loading_Image = (Image) layout.findComponentById(ResourceTable.Id_Right_Loading_Image); + + But_Group = (RadioContainer) layout.findComponentById(ResourceTable.Id_But_Group); + MACD_But = (RadioButton) layout.findComponentById(ResourceTable.Id_MACD_But); + RSI_But = (RadioButton) layout.findComponentById(ResourceTable.Id_RSI_But); + KDJ_But = (RadioButton) layout.findComponentById(ResourceTable.Id_KDJ_But); + BOLL_But = (RadioButton) layout.findComponentById(ResourceTable.Id_BOLL_But); + } + + /** + * loadData + */ + public void loadData() { + + } + + @Override + public void destroyPageFromContainer(ComponentContainer componentContainer, int i, Object o) { + componentContainer.removeComponent((Component) o); + } + + @Override + public boolean isPageMatchToObject(Component component, Object o) { + return true; + } + + private boolean contains(RectFloat rectFloat, float x, float y) { + return rectFloat.left < rectFloat.right && rectFloat.top < rectFloat.bottom + && x >= rectFloat.left && x < rectFloat.right && y >= rectFloat.top && y < rectFloat.bottom; + } - KLineFragment fragment = new KLineFragment(); - fragment.setArguments(args); + /** + * newInstance + * + * @param mContext + * @param kLineType + * @param rc + * @return PagerProvider + */ + public static PagerProvider newInstance(Context mContext, StockPresenter.KLineType kLineType, RefreshComponent rc) { + PagerProvider fragment = new PagerProvider(mContext, kLineType); + refreshComponent = rc; return fragment; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java b/entry/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java similarity index 66% rename from app/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java rename to entry/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java index 855595069bbaef43145c4090a25af5747f904795..ab6dd363402aa02485039cdeae302b8c564646ef 100644 --- a/app/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java +++ b/entry/src/main/java/com/wordplat/quickstart/utils/SSLHelper.java @@ -1,9 +1,11 @@ package com.wordplat.quickstart.utils; -import android.content.Context; -import android.util.Log; -import com.wordplat.quickstart.R; +import com.wordplat.quickstart.ResourceTable; + +import ohos.app.Context; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; import java.io.InputStream; import java.security.KeyStore; @@ -18,18 +20,22 @@ import javax.net.ssl.TrustManagerFactory; *

Date: 2017/5/29

* * @author afon + * @since 2017-05-29 */ - public class SSLHelper { + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "SSLHelper"); private static final String KEY_STORE_TYPE_BKS = "bks"; // 证书类型 固定值 private static final String KEY_STORE_TYPE_P12 = "PKCS12"; // 证书类型 固定值 - private static final int KEY_STORE_CLIENT_PATH = R.raw.client; // 客户端要给服务器端认证的证书 + private static final int KEY_STORE_CLIENT_PATH = ResourceTable.Profile_client; // 客户端要给服务器端认证的证书 private static final String KEY_STORE_PASSWORD = "123456"; // 证书密码 - private static final int KEY_STORE_TRUST_PATH = R.raw.mytruststore; // 客户端验证服务器端的证书库 + private static final int KEY_STORE_TRUST_PATH = ResourceTable.Profile_mytruststore; // 客户端验证服务器端的证书库 private static final String KEY_STORE_TRUST_PASSWORD = "123456"; // 证书库密码 + private SSLHelper() { + } + /** * 获取SSLContext * @@ -38,26 +44,32 @@ public class SSLHelper { */ public static SSLSocketFactory getSSLSocketFactory(Context context) { try { - // 服务器端需要验证的客户端证书 + /** + * 服务器端需要验证的客户端证书 + */ KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); - // 客户端信任的服务器端证书 + /** + * 客户端信任的服务器端证书 + */ KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS); - InputStream ksIn = context.getResources().openRawResource(KEY_STORE_CLIENT_PATH); - InputStream tsIn = context.getResources().openRawResource(KEY_STORE_TRUST_PATH); + InputStream ksIn = context.getResourceManager().getResource(KEY_STORE_CLIENT_PATH); + InputStream tsIn = context.getResourceManager().getResource(KEY_STORE_TRUST_PATH); try { keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); } catch (Exception e) { - e.printStackTrace(); + String ee = e.toString(); } finally { try { ksIn.close(); } catch (Exception ignore) { + String ee = ignore.toString(); } try { tsIn.close(); } catch (Exception ignore) { + String ee = ignore.toString(); } } @@ -70,9 +82,8 @@ public class SSLHelper { sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); return sslContext.getSocketFactory(); - } catch (Exception e) { - Log.e("SSLHelper", e.getMessage(), e); + HiLog.error(LABEL, e.getMessage()); } return null; } diff --git a/entry/src/main/java/com/wordplat/quickstart/utils/ToastUtil.java b/entry/src/main/java/com/wordplat/quickstart/utils/ToastUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..00d681808bb655c9101925fd96d63803ac807720 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/utils/ToastUtil.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.utils; + +import com.wordplat.quickstart.ResourceTable; + +import ohos.agp.components.*; +import ohos.agp.utils.LayoutAlignment; +import ohos.agp.window.dialog.ToastDialog; +import ohos.app.Context; + +/** + * ToastUtil + * + * @since 2021-03-29 + */ +public class ToastUtil { + private static final int NUM_5000 = 5000; + private static final int RADIOUS = 58; + private static final int NUM_50 = 50; + Context context; + + /** + * ToastUtil + * + * @param context + */ + public ToastUtil(Context context) { + this.context = context; + } + + /** + * toastCtx + * + * @param ctx + * @param text + */ + public static void toastCtx(Context ctx, String text) { + new ToastDialog(ctx) + .setText(text) + .setSize(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT) + .setAutoClosable(true) + .setDuration(NUM_5000) + .setCornerRadius(RADIOUS) + .show(); + } + + /** + * toastCtx + * + * @param ctx + * @param layout + */ + public static void toastCtx(Context ctx, Component layout) { + Component customToastLayout = (Component) LayoutScatter.getInstance(ctx).parse(layout.getId(), null, false); + ToastDialog toastDialog = new ToastDialog(ctx); + toastDialog.setComponent(customToastLayout); + toastDialog.setCornerRadius(RADIOUS); + toastDialog.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT); + toastDialog.setAlignment(LayoutAlignment.CENTER); + toastDialog.show(); + } + + /** + * toast + * + * @param text + */ + public void toast(String text) { + new ToastDialog(context) + .setText(text) + .setSize(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT) + .setAutoClosable(true) + .setDuration(NUM_5000) + .setCornerRadius(RADIOUS) + .show(); + } + + /** + * toast + * + * @param text + * @param duration + */ + public void toast(String text, int duration) { + new ToastDialog(context) + .setText(text) + .setSize(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT) + .setDuration(duration) + .setCornerRadius(RADIOUS) + .show(); + } + + /** + * toast + * + * @param text + * @param duration + * @param offsetX + * @param offsetY + * @param gravity + */ + public void toast(String text, int duration, int offsetX, int offsetY, int gravity) { + new ToastDialog(context) + .setText(text) + .setSize(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT) + .setAlignment(gravity) + .setDuration(duration) + .setCornerRadius(RADIOUS) + .setOffset(offsetX, offsetY) + .show(); + } + + /** + * toast + * + * @param context + * @param text + */ + public static void toast(Context context, String text) { + ComponentContainer customToastLayout = (ComponentContainer) LayoutScatter.getInstance(context) + .parse(ResourceTable.Layout_toast_dialog_layout, null, false); + + Text textView = (Text) customToastLayout.findComponentById(ResourceTable.Id_toast_text); + + textView.setText(text); + + DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig( + DirectionalLayout.LayoutConfig.MATCH_CONTENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT); + layoutConfig.setMarginBottom(NUM_50); + customToastLayout.setLayoutConfig(layoutConfig); + + ToastDialog toastDialog = new ToastDialog(context); + toastDialog.setComponent(customToastLayout); + toastDialog.setCornerRadius(RADIOUS); + toastDialog.setDuration(NUM_5000); + toastDialog.setSize(ComponentContainer.LayoutConfig.MATCH_CONTENT, + DirectionalLayout.LayoutConfig.MATCH_CONTENT); + toastDialog.setAlignment(LayoutAlignment.BOTTOM | LayoutAlignment.HORIZONTAL_CENTER); + toastDialog.show(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/utils/ToastViewDialog.java b/entry/src/main/java/com/wordplat/quickstart/utils/ToastViewDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..4eeede168c31ed36127c864fad679a8571861ec9 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/utils/ToastViewDialog.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.utils; + +import ohos.agp.components.Component; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.LayoutScatter; +import ohos.agp.utils.LayoutAlignment; +import ohos.agp.window.dialog.ToastDialog; +import ohos.app.Context; + +/** + * ToastViewDialog + * + * @since 2021-03-29 + */ +public class ToastViewDialog { + /** + * toast + * + * @param context + */ + public static void toast(Context context) { + new ToastDialog(context) + .setText("这是一个简单的弹窗") + .setAlignment(LayoutAlignment.CENTER) + .show(); + } + + /** + * toast + * + * @param context + * @param text + */ + public static void toast(Context context, String text) { + ToastUtil.toast(context, "" + text); + } + + /** + * toast + * + * @param context + * @param layout + */ + public static void toast(Context context, Component layout) { + Component customToastLayout = (Component) LayoutScatter.getInstance(context).parse(layout.getId(), null, false); + ToastDialog toastDialog = new ToastDialog(context); + toastDialog.setComponent(customToastLayout); + toastDialog.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT); + toastDialog.setAlignment(LayoutAlignment.CENTER); + toastDialog.show(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadBaseComponent.java b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadBaseComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..c4d0691542823cb034dd3275dbe824f640137d7d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadBaseComponent.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.PullLayout.pulllistview; + +import ohos.agp.components.AttrSet; +import ohos.agp.components.ComponentContainer; +import ohos.app.Context; + +/** + * HeadBaseComponent + * + * @since 2021-04-22 + */ +public abstract class HeadBaseComponent extends ComponentContainer { + /** + * 触发下拉刷新时的最小高度,当刚好下拉到这个距离,那就直接刷新, + * 如果下拉的距离超过了这个距离,那就先滚动到这个距离,然后才开始刷新 + */ + public int mPullRefreshHeight; + /** + * 最小阻尼,用户越往下拉,越不跟手 + */ + public float minDamp = 1.6f; + /** + * 最大阻尼 + */ + public float maxDamp = 2.2f; + /** + * RefreshState + * + * @since 2021-04-22 + */ + public enum RefreshState { + /** + * 初始状态 + */ + STATE_INIT, + /** + * 下拉刷新的头部可见 + */ + STATE_VISIBLE, + /** + * 正在刷新的状态 + */ + STATE_REFRESH, + /** + * 超出可刷新距离的状态 + */ + STATE_OVER, + /** + * 超出刷新位置松开手后的状态 + */ + STATE_OVER_RELEASE + } + /** + * 默认状态为 初始化状态 + */ + protected RefreshState mState = RefreshState.STATE_INIT; + + /** + * HeadBaseComponent + * + * @param context + */ + public HeadBaseComponent(Context context) { + this(context, null); + } + + /** + * HeadBaseComponent + * + * @param context + * @param attrSet + */ + public HeadBaseComponent(Context context, AttrSet attrSet) { + this(context, attrSet, ""); + } + + /** + * HeadBaseComponent + * + * @param context + * @param attrSet + * @param styleName + */ + public HeadBaseComponent(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + init(); + } + + /** + * init + */ + public abstract void init(); + + /** + * onHeadVisible + */ + public abstract void onHeadVisible(); + + /** + * onHeadOver + */ + public abstract void onHeadOver(); + + /** + * onRefresh + */ + public abstract void onRefresh(); + + /** + * onFinish + */ + public abstract void onFinish(); + + /** + * setState + * + * @param state + */ + public void setState(RefreshState state) { + this.mState = state; + } + + /** + * getState + * + * @return RefreshState + */ + public RefreshState getState() { + return mState; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadDefaultComponent.java b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadDefaultComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..f716a224cff463697a892b95774e591f9bdda7d6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/HeadDefaultComponent.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.PullLayout.pulllistview; + +import com.wordplat.quickstart.ResourceTable; + +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.app.Context; + +/** + * HeadDefaultComponent + * + * @since 2021-04-22 + */ +public class HeadDefaultComponent extends HeadBaseComponent { + private DirectionalLayout directionalLayout; + private Image mImage; + private Text mTextTitle; + private Text mTextTime; + private AnimatorProperty mAnimator; + + /** + * HeadDefaultComponent + * + * @param context + */ + public HeadDefaultComponent(Context context) { + this(context, null); + } + + /** + * HeadDefaultComponent + * + * @param context + * @param attrSet + */ + public HeadDefaultComponent(Context context, AttrSet attrSet) { + this(context, attrSet, ""); + } + + /** + * HeadDefaultComponent + * + * @param context + * @param attrSet + * @param styleName + */ + public HeadDefaultComponent(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + } + + @Override + public void init() { + LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_head_default_layout, this, true); + directionalLayout = (DirectionalLayout) findComponentById(ResourceTable.Id_dl_default_all); + mTextTitle = (Text) findComponentById(ResourceTable.Id_text_head_title); + mImage = (Image) findComponentById(ResourceTable.Id_img_head_default); + mTextTime = (Text) findComponentById(ResourceTable.Id_text_default_value); + } + + @Override + public void onHeadVisible() { + } + + @Override + public void onHeadOver() { + } + + @Override + public void onRefresh() { + mAnimator = mImage.createAnimatorProperty(); + mAnimator.setDuration(2000).rotate(360).setLoopedCount(AnimatorProperty.INFINITE).setTarget(mImage).start(); + } + + @Override + public void onFinish() { + mAnimator.cancel(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/IRefresh.java b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/IRefresh.java new file mode 100644 index 0000000000000000000000000000000000000000..6903d9f26e0d3ef9d6c451e28c248930fd7c6b99 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/IRefresh.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.PullLayout.pulllistview; + +/** + * IRefresh + * + * @since 2021-04-22 + */ +public interface IRefresh { + /** + * refreshFinish + */ + void refreshFinish(); + + /** + * setRefreshListener + * + * @param listener + */ + void setRefreshListener(RefreshListener listener); + + /** + * setHeadComponent + * + * @param headComponent + */ + void setHeadComponent(HeadBaseComponent headComponent); + + /** + * RefreshListener + * + * @since 2021-04-22 + */ + interface RefreshListener { + /** + * 告诉调用者,此时正在刷新 + */ + void onRefresh(); + + /** + * 是否允许刷新 + * + * @return + */ + boolean enableRefresh(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/OnRefreshComponent.java b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/OnRefreshComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..284e5a1a2bc1456800bac41d2888e157a2fa36e6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/OnRefreshComponent.java @@ -0,0 +1,6 @@ +package com.wordplat.quickstart.wight.PullLayout.pulllistview; + +public interface OnRefreshComponent { + void refresh(boolean isRefresh); +} + diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/RefreshComponent.java b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/RefreshComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..386d6f050eeb7406c3bb8398ba17a1fef0851ec6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/PullLayout/pulllistview/RefreshComponent.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.PullLayout.pulllistview; + +import com.wordplat.ikvstockchart.InteractiveKLineView; +import com.wordplat.quickstart.ResourceTable; +import com.wordplat.quickstart.app.DeviceRuntime; +import ohos.agp.components.AttrSet; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.Text; +import ohos.app.Context; +import ohos.data.DatabaseHelper; +import ohos.data.preferences.Preferences; +import ohos.multimodalinput.event.TouchEvent; + +/** + * RefreshComponent + * + * @since 2021-04-22 + */ +public class RefreshComponent extends ComponentContainer implements Component.LayoutRefreshedListener, IRefresh, OnRefreshComponent { + protected HeadBaseComponent headBaseComponent; + + RefreshListener refreshListener; + private InteractiveKLineView interactiveKLineView; + private float startDownY; + private int mLastY; + private HeadBaseComponent.RefreshState mState; + private long startTime = 0; + private long endTime = 0; + private Preferences preferences; + private Boolean isPull = true; + + /** + * RefreshComponent + * + * @param context + */ + public RefreshComponent(Context context) { + super(context); + } + + /** + * RefreshComponent + * + * @param context + * @param attrSet + */ + public RefreshComponent(Context context, AttrSet attrSet) { + super(context, attrSet); + TimePreferences(); + setLayoutRefreshedListener(this); + } + + /** + * setKLineView + * + * @param kLineView + */ + public void setKLineView(InteractiveKLineView kLineView) { + interactiveKLineView = kLineView; + interactiveKLineView.setOnTouchInterface(new InteractiveKLineView.ViewOnTouchInterface() { + @Override + public boolean touch(int getAction, float endUpY) { + Component head = getComponentAt(0); + Component content = getComponentAt(1); + Text mTextTitle = (Text) head.findComponentById(ResourceTable.Id_text_head_title); + Text mTextTime = (Text) findComponentById(ResourceTable.Id_text_default_value); + switch (getAction) { + case TouchEvent.CANCEL: + break; + case TouchEvent.PRIMARY_POINT_UP: + /** + * 手指抬起之后, 如果状态不是刷新,判断下拉的高度是否大于头部高度 如果是则回弹,开始刷新,如果没有则回到初始状态,隐藏头部 + */ + if (mState != HeadBaseComponent.RefreshState.STATE_REFRESH) { + /** + * 如果状态不是刷新,判断下拉的高度是否大于头部高度 如果是则回弹, 开始刷新,如果没有则回到初始状态,隐藏头部 + */ + if (refreshListener != null && head.getBottom() > headBaseComponent.mPullRefreshHeight) { + head.setComponentPosition(head.getLeft(), 0, head.getRight(), head.getHeight()); + content.setComponentPosition(content.getLeft(), head.getHeight(), + content.getRight(), content.getBottom()); + headBaseComponent.onRefresh(); + refreshListener.onRefresh(); + mState = HeadBaseComponent.RefreshState.STATE_REFRESH; + } else { + head.setComponentPosition(head.getLeft(), -head.getHeight(), + head.getRight(), content.getTop()); + content.setComponentPosition(content.getLeft(), 0, + content.getRight(), content.getBottom()); + mState = HeadBaseComponent.RefreshState.STATE_INIT; + content.setEnabled(true); + } + } + isPull = true; + preferences.putLong(DeviceRuntime.PULL_START, System.currentTimeMillis()); + break; + case TouchEvent.OTHER_POINT_DOWN: + /** + * 表示当一个或多个手指已经触摸屏幕时,另一根手指触摸了屏幕。 + */ + startDownY = endUpY; + break; + case TouchEvent.OTHER_POINT_UP: + /** + * 表示有些手指从屏幕上抬起,而另一些手指则留在屏幕上。 + */ + startDownY = endUpY; + break; + case TouchEvent.PRIMARY_POINT_DOWN: + /** + * 表示第一根手指触摸屏幕。 + */ + startDownY = endUpY; + break; + case TouchEvent.POINT_MOVE: + /** + * 指示手指在屏幕上移动。 + * 1.正在刷新 2.未刷新 + */ + final float scrollY = endUpY - startDownY; + if (scrollY > 0) { + if (content.getScrollValue(AXIS_Y) > 0) { + content.setEnabled(true); + break; + } + content.setEnabled(false); + float offsetY; + + if (head.getTop() < headBaseComponent.mPullRefreshHeight) { + /** + * 没到可刷新的距离,减少阻尼 + */ + offsetY = (int) (mLastY / 2f); + } else { + /** + * 达到可刷新的距离,加大阻尼 + */ + offsetY = (int) (mLastY / 3.2f); + } + + startDownY = endUpY; + mLastY = (int) scrollY; + + if (mState != HeadBaseComponent.RefreshState.STATE_REFRESH) { + /** + * 未刷新时 1.头部正在下拉,并未完全显示, 2.头部已经下拉并且超出自身高度 + */ + if (head.getBottom() > 0 && head.getBottom() < headBaseComponent.mPullRefreshHeight) { + /** + * 1.头部正在下拉,并未完全显示, + */ + mTextTitle.setText("下拉"); + headBaseComponent.onHeadVisible(); + } else if (head.getBottom() > headBaseComponent.mPullRefreshHeight) { + /** + * 2.头部已经下拉并且超出自身高度 + */ + mTextTitle.setText("刷新释放"); + startTime = preferences.getLong(DeviceRuntime.PULL_START, 0); + endTime = System.currentTimeMillis(); + if (startTime != 0 && isPull) { + long cutTime = endTime - startTime; + mTextTime.setVisibility(VISIBLE); + mTextTime.setText("最后更新:" + cutStringTime(cutTime/1000) + "之前"); + isPull = false; + } + headBaseComponent.onHeadOver(); + } + } + content.setTop((int) (content.getTop() + offsetY)); + head.setComponentPosition(0, content.getTop() - head.getHeight(), + head.getRight(), content.getTop()); + content.setComponentPosition(0, content.getTop(), content.getRight(), + content.getBottom()); + break; + } else if (scrollY < 0) { + if (content.getTop() > 0 && content.getScrollValue(AXIS_Y) == 0) { + content.setEnabled(false); + float offsetY; + if (head.getTop() < headBaseComponent.mPullRefreshHeight) { + /** + * 没到可刷新的距离,减少阻尼 + */ + mTextTitle.setText("下拉"); + offsetY = (int) (mLastY / 2f); + } else { + /** + * 达到可刷新的距离,加大阻尼 + */ + mTextTitle.setText("刷新释放"); + offsetY = (int) (mLastY / 3.2f); + } + startDownY = endUpY; + mLastY = (int) scrollY; + content.setTop((int) (content.getTop() + offsetY)); + content.setComponentPosition(content.getLeft(), content.getTop(), + content.getRight(), content.getBottom()); + break; + } else { + head.setComponentPosition(head.getLeft(), -head.getHeight(), head.getRight(), 0); + content.setComponentPosition(content.getLeft(), 0, content.getRight(), + content.getBottom()); + content.setEnabled(true); + break; + } + } + break; + default: + break; + } + return false; + } + }); + } + + @Override + public void onRefreshed(Component component) { + Component head = getComponentAt(0); + headBaseComponent.mPullRefreshHeight = head.getHeight(); + Component content = getComponentAt(1); + head.setComponentPosition(0, content.getTop() - head.getHeight(), head.getRight(), content.getTop()); + content.setComponentPosition(0, content.getTop(), content.getRight(), content.getBottom()); + } + + @Override + public void refreshFinish() { + headBaseComponent.onFinish(); + headBaseComponent.setState(HeadBaseComponent.RefreshState.STATE_INIT); + Component head = getComponentAt(0); + Component content = getComponentAt(1); + content.setEnabled(true); + if (head.getBottom() > 0) { + head.setComponentPosition(head.getLeft(), -head.getHeight(), head.getRight(), 0); + content.setComponentPosition(content.getLeft(), 0, content.getRight(), content.getHeight()); + } + mState = HeadBaseComponent.RefreshState.STATE_INIT; + } + + @Override + public void setRefreshListener(RefreshListener listener) { + refreshListener = listener; + } + + @Override + public void setHeadComponent(HeadBaseComponent headComponent) { + if (headBaseComponent != null) { + removeComponent(headBaseComponent); + } + headBaseComponent = headComponent; + LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT); + addComponent(headBaseComponent, 0, config); + } + + /** + * 刷新时间存储 + */ + private void TimePreferences() { + DatabaseHelper databaseHelper = new DatabaseHelper(getContext()); // context入参类型为ohos.app.Context。 + String fileName = "PULL_TIME"; // fileName表示文件名,其取值不能为空,也不能包含路径,默认存储目录可以通过context.getPreferencesDir()获取。 + preferences = databaseHelper.getPreferences(fileName); + preferences.putLong(DeviceRuntime.PULL_START, 0); + } + + private String cutStringTime(long mss) { + String DateTimes = null; + long days = mss / (60 * 60 * 24); + long hours = (mss % (60 * 60 * 24)) / (60 * 60); + long minutes = (mss % (60 * 60)) / 60; + long seconds = mss % 60; + if (days > 0) { + DateTimes = days + "天" + hours + "小时" + minutes + "分钟" + + seconds + "秒"; + } else if (hours > 0) { + DateTimes = hours + "小时" + minutes + "分钟" + + seconds + "秒"; + } else if (minutes > 0) { + DateTimes = minutes + "分钟" + + seconds + "秒"; + } else { + DateTimes = seconds + "秒"; + } + + return DateTimes; + } + + @Override + public void refresh(boolean isRefresh) { + Component head = getComponentAt(0); + Component content = getComponentAt(1); + head.setComponentPosition(head.getLeft(), 0, head.getRight(), head.getHeight()); + content.setComponentPosition(content.getLeft(), head.getHeight(), + content.getRight(), content.getBottom()); + headBaseComponent.onRefresh(); + refreshListener.onRefresh(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/navigation/AttrUtils.java b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/AttrUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..dc3c0003ec4cd04de254d726ae812b4c2245b851 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/AttrUtils.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.navigation; + +import ohos.agp.components.AttrSet; +import ohos.agp.components.element.Element; + +/** + * get attr value class + * + * @since 2021-05-08 + */ +public class AttrUtils { + private AttrUtils() { + } + + /** + * get the int value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return int value + */ + public static int getIntFromAttr(AttrSet attrs, String name, int defaultValue) { + int value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getIntegerValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the float value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return float value + */ + public static float getFloatFromAttr(AttrSet attrs, String name, float defaultValue) { + float value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getFloatValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the boolean value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return boolean value + */ + public static boolean getBooleanFromAttr(AttrSet attrs, String name, boolean defaultValue) { + boolean value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getBoolValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the long value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return long value + */ + public static long getLongFromAttr(AttrSet attrs, String name, long defaultValue) { + long value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getLongValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the color value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return int colorValue + */ + public static int getColorFromAttr(AttrSet attrs, String name, int defaultValue) { + int value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getColorValue().getValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the dimensionValue value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return int dimensionValue + */ + public static int getDimensionFromAttr(AttrSet attrs, String name, int defaultValue) { + int value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getDimensionValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the String value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return String value + */ + public static String getStringFromAttr(AttrSet attrs, String name, String defaultValue) { + String value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getStringValue(); + } + } catch (Exception e) { + return value; + } + return value; + } + + /** + * get the Element value from AttrSet + * + * @param attrs the attrSet + * @param name the attrName + * @param defaultValue the defaultValue + * @return Element value + */ + public static Element getElementFromAttr(AttrSet attrs, String name, Element defaultValue) { + Element value = defaultValue; + try { + if (attrs.getAttr(name).isPresent() && attrs.getAttr(name).get() != null) { + value = attrs.getAttr(name).get().getElement(); + } + } catch (Exception e) { + return value; + } + return value; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigation.java b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigation.java new file mode 100644 index 0000000000000000000000000000000000000000..1a514745ce44467a92027ce91b05030ee79835b1 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigation.java @@ -0,0 +1,1339 @@ +package com.wordplat.quickstart.wight.navigation; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorValue; +import ohos.agp.components.AttrSet; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.render.*; +import ohos.agp.utils.Color; +import ohos.agp.utils.Point; +import ohos.agp.utils.RectFloat; +import ohos.app.Context; +import ohos.media.image.PixelMap; +import ohos.multimodalinput.event.TouchEvent; + +import java.util.ArrayList; +import java.util.List; + +public class BottomNavigation extends Component implements Component.DrawTask, Component.TouchEventListener { + private static final int MIN_ITEMS = 3; + private static final int MAX_ITEMS = 5; + + public enum TitleState { + /** + * SHOW_WHEN_ACTIVE + */ + SHOW_WHEN_ACTIVE, + /** + * SHOW_WHEN_ACTIVE_FORCE + */ + SHOW_WHEN_ACTIVE_FORCE, + /** + * ALWAYS_SHOW + */ + ALWAYS_SHOW, + /** + * ALWAYS_HIDE + */ + ALWAYS_HIDE + } + + private static final int DEFAULT_ANIMATION_DURATION = 200; + + private OnTabSelectedListener tabSelectedListener; + private OnNavigationPositionListener navigationPositionListener; + + private ArrayList items = new ArrayList<>(); + + private int defaultBackgroundColor = Color.WHITE.getValue(); + + private int itemActiveColor; + private int itemInactiveColor; + private int titleColorActive; + private int itemDisableColor; + private int titleColorInactive; + private int coloredTitleColorActive; + private int coloredTitleColorInactive; + private int bottomNavigationHeight; + private float selectedItemWidth; + private float notSelectedItemWidth; + private float offsetX; + private boolean forceTint = true; + private TitleState titleState = TitleState.SHOW_WHEN_ACTIVE; + + private Boolean[] itemsEnabledStates = {true, true, true, true, true}; + + private boolean colored = false; + + private boolean selectedBackgroundVisible = false; + + private List notifications = Notification.generateEmptyList(MAX_ITEMS); + + private int notificationMarginLeft; + private int notificationMarginTop; + + private int notificationTextColor = Color.WHITE.getValue(); + private int notificationBackgroundColor = 0xffff5454; + + private int mElevation; + + private boolean isFirst = true; + private boolean needInit = true; + private boolean mIsHidden = false; + + private boolean selectHideNotification = false; + + private Paint mPaint = new Paint(); + private Paint elevationPaint = new Paint(); + private Paint selectBackgroundPaint = new Paint(); + private Paint mOverPaint = new Paint(); + private Paint mTextPaint = new Paint(); + private Paint notificationBackgroundPaint = new Paint(); + private Paint notificationTextPaint = new Paint(); + + private int lastPosition; + private int mPosition; + private int downPosition; + + private AnimatorValue changeAnimatorValue; + private AnimatorValue overAnimatorValue; + private float changePose = 1; + private float overPose = 1; + + private int mAnimationDuration = DEFAULT_ANIMATION_DURATION; + private int mRippleAnimationDuration = (int) (DEFAULT_ANIMATION_DURATION * 2.5); + + private AnimatorValue showAnimatorValue; + private int showBottom = 0; + private long showDurationTime = 300; + + private int iconSize = dp2px(24); + + private int alwaysHideTop = dp2px(16); + private int alwaysShowTopActive = dp2px(6); + private int alwaysShowTopInactive = dp2px(8); + private int showWhenActiveForceTopActive = dp2px(6); + private int showWhenActiveForceTopInactive = dp2px(16); + + private int titleActiveTextSize = 0; + private int titleInactiveTextSize = 0; + + private int defaultTitleActiveTextSize = dp2px(14); + private int defaultTitleInactiveTextSize = dp2px(12); + private int defaultTitleActiveSmallTextSize = dp2px(11); + private int defaultTitleInactiveSmallTextSize = dp2px(10); + + private int textCenterY = dp2px(20); + + private boolean touchEffect; + + /** + * BottomNavigation + * + * @param context + */ + public BottomNavigation(Context context) { + this(context, null, null); + } + + /** + * BottomNavigation + * + * @param context + * @param attrSet + */ + public BottomNavigation(Context context, AttrSet attrSet) { + this(context, attrSet, null); + } + + /** + * BottomNavigation + * + * @param context + * @param attrSet + * @param styleName + */ + public BottomNavigation(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + init(context, attrSet); + } + + private void init(Context context, AttrSet attrSet) { + selectedBackgroundVisible = AttrUtils.getBooleanFromAttr(attrSet, "selectedBackgroundVisible", false); + + titleColorActive = AttrUtils.getColorFromAttr(attrSet, "accentColor", Color.rgb(47, 140, 213)); + titleColorInactive = AttrUtils.getColorFromAttr(attrSet, "inactiveColor", 0xff747474); + itemDisableColor = AttrUtils.getColorFromAttr(attrSet, "disableColor", 0x3A000000); + + coloredTitleColorActive = AttrUtils.getColorFromAttr(attrSet, "coloredActive", Color.rgb(47, 140, 213)); + coloredTitleColorInactive = AttrUtils.getColorFromAttr(attrSet, "coloredInactive", 0x50FFFFFF); + + colored = AttrUtils.getBooleanFromAttr(attrSet, "colored", false); + + mElevation = AttrUtils.getDimensionFromAttr(attrSet, "elevation", dp2px(0)); + + notificationTextColor = Color.WHITE.getValue(); + bottomNavigationHeight = dp2px(40); + + itemActiveColor = titleColorActive; + itemInactiveColor = titleColorInactive; + + notificationMarginLeft = dp2px(16); + notificationMarginTop = 0; + + mPaint.setAntiAlias(true); + elevationPaint.setAntiAlias(true); + mOverPaint.setAntiAlias(true); + mTextPaint.setAntiAlias(true); + selectBackgroundPaint.setAntiAlias(true); + notificationBackgroundPaint.setAntiAlias(true); + notificationTextPaint.setAntiAlias(true); + + setTouchEventListener(this); + addDrawTask(this); + } + + @Override + public void onDraw(Component component, Canvas canvas) { + if (isFirst) { + isFirst = false; + ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig(); + layoutConfig.setMarginBottom(0); + layoutConfig.height = bottomNavigationHeight + mElevation; + setLayoutConfig(layoutConfig); + } + if (items != null && items.size() > 0) { + if (needInit) { + needInit = false; + getMeasurements(); + } + drawBackground(canvas); + drawItems(canvas); + } + } + + private void drawItems(Canvas canvas) { + canvas.save(); + canvas.translate(offsetX, mElevation); + if (selectedBackgroundVisible && (!colored || overPose == 1)) { + selectBackgroundPaint.setColor(new Color(0x17000000)); + canvas.drawRect( + new RectFloat( + getCurrentCenterX(mPosition, mPosition) - selectedItemWidth / 2, + 0, + getCurrentCenterX(mPosition, mPosition) + selectedItemWidth / 2, + bottomNavigationHeight), + selectBackgroundPaint); + } + for (int i = 0; i < items.size(); i++) { + BottomNavigationItem bottomNavigationItem = items.get(i); + float topOff = 0; + if (titleState == TitleState.ALWAYS_HIDE) { + topOff = alwaysHideTop; + } else if (titleState == TitleState.ALWAYS_SHOW + || titleState == TitleState.SHOW_WHEN_ACTIVE && items.size() <= MIN_ITEMS) { + if (i != lastPosition && i != mPosition) { + topOff = alwaysShowTopInactive; + } else if (i == mPosition) { + topOff = + alwaysShowTopInactive + + (alwaysShowTopActive - alwaysShowTopInactive) * changePose; + } else if (i == lastPosition) { + topOff = + alwaysShowTopActive + + (alwaysShowTopInactive - alwaysShowTopActive) * changePose; + } + } else { + if (i != lastPosition && i != mPosition) { + topOff = showWhenActiveForceTopInactive; + } else if (i == mPosition) { + topOff = + showWhenActiveForceTopInactive + + (showWhenActiveForceTopActive - showWhenActiveForceTopInactive) + * changePose; + } else if (i == lastPosition) { + topOff = + showWhenActiveForceTopActive + + (showWhenActiveForceTopInactive - showWhenActiveForceTopActive) + * changePose; + } + } + PixelMap pixelMap = bottomNavigationItem.getOrgPixelMap(); + float centerX = + getCurrentCenterX(i, lastPosition) + + (getCurrentCenterX(i, mPosition) - getCurrentCenterX(i, lastPosition)) + * changePose; + if (touchEffect && i == downPosition) { + mPaint.setColor(new Color(0x20000000)); + canvas.drawCircle( + centerX, bottomNavigationHeight / 2, notSelectedItemWidth * 0.6f, mPaint); + } + + if (forceTint) { + if (colored) { + if (i == mPosition) { + mTextPaint.setColor(new Color(coloredTitleColorActive)); + pixelMap = bottomNavigationItem.getColoredActivePixelMap(); + } else if (!itemsEnabledStates[i]) { + mTextPaint.setColor(new Color(itemDisableColor)); + pixelMap = bottomNavigationItem.getDisablePixelMap(); + } else { + mTextPaint.setColor(new Color(coloredTitleColorInactive)); + pixelMap = bottomNavigationItem.getColoredInActivePixelMap(); + } + } else { + if (i == mPosition) { + mTextPaint.setColor(new Color(titleColorActive)); + pixelMap = bottomNavigationItem.getActivePixelMap(); + } else if (!itemsEnabledStates[i]) { + mTextPaint.setColor(new Color(itemDisableColor)); + pixelMap = bottomNavigationItem.getDisablePixelMap(); + } else { + mTextPaint.setColor(new Color(titleColorInactive)); + pixelMap = bottomNavigationItem.getInActivePixelMap(); + } + } + } + + if (pixelMap != null) { + RectFloat rectFloat = new RectFloat(); + rectFloat.left = + getCurrentCenterX(i, lastPosition) + + (getCurrentCenterX(i, mPosition) - getCurrentCenterX(i, lastPosition)) + * changePose + - iconSize / 2; + rectFloat.right = + getCurrentCenterX(i, lastPosition) + + (getCurrentCenterX(i, mPosition) - getCurrentCenterX(i, lastPosition)) + * changePose + + iconSize / 2; + + rectFloat.top = topOff; + rectFloat.bottom = topOff + iconSize; + mPaint.setColor(new Color(0xffffffff)); + canvas.drawPixelMapHolderRect( + new PixelMapHolder(pixelMap), + new RectFloat( + 0, + 0, + pixelMap.getImageInfo().size.width, + pixelMap.getImageInfo().size.height), + rectFloat, + mPaint); + } + + if (titleState != TitleState.ALWAYS_HIDE) { + float textSize = 0; + float alpha = mTextPaint.getAlpha(); + int activeSize = titleActiveTextSize; + int inactiveSize = titleInactiveTextSize; + if (activeSize == 0 || inactiveSize == 0) { + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { + activeSize = defaultTitleActiveSmallTextSize; + inactiveSize = defaultTitleInactiveSmallTextSize; + } else { + activeSize = defaultTitleActiveTextSize; + inactiveSize = defaultTitleInactiveTextSize; + } + } + if (titleState == TitleState.ALWAYS_SHOW + || titleState == TitleState.SHOW_WHEN_ACTIVE && items.size() <= MIN_ITEMS) { + if (i != lastPosition && i != mPosition) { + textSize = inactiveSize; + } else if (i == mPosition) { + textSize = inactiveSize + (activeSize - inactiveSize) * changePose; + } else if (i == lastPosition) { + textSize = activeSize + (inactiveSize - activeSize) * changePose; + } + } else { + textSize = activeSize; + if (i != lastPosition && i != mPosition) { + alpha = 0; + } else if (i == mPosition) { + alpha = changePose * alpha; + } else if (i == lastPosition) { + alpha = (1 - changePose) * alpha; + } + } + if (alpha != 0) { + mTextPaint.setAlpha(alpha); + mTextPaint.setTextSize((int) textSize); + float textWidth = mTextPaint.measureText(bottomNavigationItem.getTitle()); + float textHeight = mTextPaint.descent() - mTextPaint.ascent(); + canvas.drawText( + mTextPaint, + bottomNavigationItem.getTitle(), + centerX - textWidth / 2, + textCenterY + textHeight / 4); + } + } + + if (notifications != null && i < notifications.size()) { + Notification notification = notifications.get(i); + notificationBackgroundPaint.setColor( + new Color( + notification.getBackgroundColor() == 0 + ? notificationBackgroundColor + : notification.getBackgroundColor())); + notificationTextPaint.setColor( + new Color( + notification.getTextColor() == 0 + ? notificationTextColor + : notification.getTextColor())); + notificationTextPaint.setTextSize(dp2px(9)); + + if (!notification.isEmpty()) { + float textWidth = notificationTextPaint.measureText(notification.getText()); + float textHeight = notificationTextPaint.descent() - notificationTextPaint.ascent(); + float notificationCenterX = centerX; + float marginTop = topOff + notificationMarginTop; + notificationCenterX = centerX + notificationMarginLeft; + RectFloat rectFloat = new RectFloat(); + if (textWidth > textHeight) { + rectFloat.left = notificationCenterX - textWidth / 2 - dp2px(1); + rectFloat.right = notificationCenterX + textWidth / 2 + dp2px(1); + } else { + rectFloat.left = notificationCenterX - textHeight / 2 - dp2px(1); + rectFloat.right = notificationCenterX + textHeight / 2 + dp2px(1); + } + rectFloat.top = marginTop - dp2px(1); + rectFloat.bottom = marginTop + textHeight + dp2px(1); + canvas.drawRoundRect(rectFloat, dp2px(8), dp2px(8), notificationBackgroundPaint); + canvas.drawText( + notificationTextPaint, + notification.getText(), + notificationCenterX - textWidth / 2, + marginTop + textHeight * 3 / 4); + } + } + } + canvas.restore(); + } + + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) { + float touchX = getTouchX(touchEvent, 0); + float touchY = getTouchY(touchEvent, 0); + if (items != null && items.size() > 0) { + int position = getPostionFromX(touchX); + if (position < itemsEnabledStates.length && itemsEnabledStates[position]) { + downPosition = position; + if (touchEffect) { + if (overAnimatorValue == null || !overAnimatorValue.isRunning()) { + invalidate(); + } + } + } + } + } else if (touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) { + if (downPosition != -1) { + float touchX = getTouchX(touchEvent, 0); + float touchY = getTouchY(touchEvent, 0); + if (getPostionFromX(touchX) == downPosition + && touchY >= 0 + && touchY <= bottomNavigationHeight) { + if (items != null && items.size() > downPosition) { + choose(downPosition, true); + downPosition = -1; + if (touchEffect) { + if (overAnimatorValue == null || !overAnimatorValue.isRunning()) { + invalidate(); + } + } + } + } + } + } else if (touchEvent.getAction() == TouchEvent.POINT_MOVE) { + if (downPosition != -1) { + float touchX = getTouchX(touchEvent, 0); + float touchY = getTouchY(touchEvent, 0); + if (getPostionFromX(touchX) != downPosition + || touchY < 0 + || touchY > bottomNavigationHeight) { + downPosition = -1; + if (items != null && items.size() > 0) { + if (touchEffect) { + if (overAnimatorValue == null || !overAnimatorValue.isRunning()) { + invalidate(); + } + } + } + } + } + } + return true; + } + + private void drawBackground(Canvas canvas) { + LinearShader gradient = + new LinearShader( + new Point[]{new Point(0, 0), new Point(0, mElevation)}, + new float[]{0f, 0.3f, 1f}, + new Color[]{new Color(0x00000000), new Color(0x00000000), new Color(0x10000000)}, + Shader.TileMode.CLAMP_TILEMODE); + + elevationPaint.setShader(gradient, Paint.ShaderType.LINEAR_SHADER); + if (mElevation > 0) { + canvas.drawRect(new RectFloat(0, 0, getWidth(), mElevation), elevationPaint); + } + + if (colored) { + if (mPosition >= 0 && mPosition < items.size()) { + if (overPose == 1) { + mPaint.setColor(new Color(items.get(mPosition).getColor())); + } else if (lastPosition >= 0 && lastPosition < items.size()) { + mOverPaint.setColor(new Color(items.get(mPosition).getColor())); + mPaint.setColor(new Color(items.get(lastPosition).getColor())); + } + } else { + mPaint.setColor(new Color(titleColorActive)); + } + } else { + mPaint.setColor(new Color(defaultBackgroundColor)); + } + + canvas.drawRect( + new RectFloat(0, mElevation, getWidth(), mElevation + bottomNavigationHeight), mPaint); + + if (colored && overPose != 1) { + canvas.saveLayer( + new RectFloat(0, mElevation, getWidth(), bottomNavigationHeight + mElevation), + mOverPaint); + canvas.drawCircle( + getCurrentCenterX(mPosition, mPosition), + bottomNavigationHeight / 2, + getWidth() * overPose, + mOverPaint); + canvas.restore(); + } + } + + private void choose(int targetPosition, boolean useCallback) { + if (targetPosition == mPosition || items == null || targetPosition >= items.size()) { + return; + } + if (changeAnimatorValue != null && changeAnimatorValue.isRunning()) { + return; + } + if (overAnimatorValue != null && overAnimatorValue.isRunning()) { + overAnimatorValue.stop(); + } + if (useCallback && tabSelectedListener != null) { + tabSelectedListener.onTabSelected(downPosition, true); + } + lastPosition = mPosition; + mPosition = targetPosition; + changePose = 0; + overPose = 0; + + if (selectHideNotification) { + setNotification((Notification) null, targetPosition); + } + + if (changeAnimatorValue == null) { + changeAnimatorValue = new AnimatorValue(); + changeAnimatorValue.setDuration(mAnimationDuration); + changeAnimatorValue.setCurveType(Animator.CurveType.LINEAR); + changeAnimatorValue.setValueUpdateListener( + new AnimatorValue.ValueUpdateListener() { + @Override + public void onUpdate(AnimatorValue animatorValue, float v) { + changePose = v; + } + }); + } + if (overAnimatorValue == null) { + overAnimatorValue = new AnimatorValue(); + overAnimatorValue.setDuration(mRippleAnimationDuration); + overAnimatorValue.setCurveType(Animator.CurveType.LINEAR); + overAnimatorValue.setValueUpdateListener( + new AnimatorValue.ValueUpdateListener() { + @Override + public void onUpdate(AnimatorValue animatorValue, float v) { + overPose = v; + getContext() + .getUITaskDispatcher() + .asyncDispatch( + new Runnable() { + @Override + public void run() { + invalidate(); + } + }); + } + }); + } + + changeAnimatorValue.start(); + overAnimatorValue.start(); + } + + private boolean isClassic() { + return titleState != TitleState.ALWAYS_HIDE + && titleState != TitleState.SHOW_WHEN_ACTIVE_FORCE + && (items.size() == MIN_ITEMS || titleState == TitleState.ALWAYS_SHOW); + } + + /** + * addItemAtIndex + * + * @param index + * @param item + */ + public void addItemAtIndex(int index, BottomNavigationItem item) { + if (this.items.size() > MAX_ITEMS) { + return; + } + if (index < items.size()) { + item.setupPixelMap(this, iconSize); + this.items.add(index, item); + } + needInit = true; + invalidate(); + } + + /** + * addItem + * + * @param item + */ + public void addItem(BottomNavigationItem item) { + if (this.items.size() > MAX_ITEMS) { + return; + } + item.setupPixelMap(this, iconSize); + items.add(item); + needInit = true; + invalidate(); + } + + /** + * addItems + * + * @param items + */ + public void addItems(List items) { + if (items.size() > MAX_ITEMS || (this.items.size() + items.size()) > MAX_ITEMS) { + return; + } + for (BottomNavigationItem item : items) { + item.setupPixelMap(this, iconSize); + } + this.items.addAll(items); + needInit = true; + invalidate(); + } + + /** + * removeItemAtIndex + * + * @param index + */ + public void removeItemAtIndex(int index) { + if (index < items.size()) { + this.items.remove(index); + needInit = true; + invalidate(); + } + } + + /** + * removeAllItems + */ + public void removeAllItems() { + this.items.clear(); + needInit = true; + invalidate(); + } + + /** + * refresh + */ + public void refresh() { + needInit = true; + invalidate(); + } + + /** + * enableItemAtPosition + * + * @param position + */ + public void enableItemAtPosition(int position) { + if (position < 0 || position > items.size() - 1) { + return; + } + itemsEnabledStates[position] = true; + refresh(); + } + + /** + * disableItemAtPosition + * + * @param position + */ + public void disableItemAtPosition(int position) { + if (position < 0 || position > items.size() - 1) { + return; + } + itemsEnabledStates[position] = false; + refresh(); + } + + /** + * setItemDisableColor + * + * @param itemDisableColor + */ + public void setItemDisableColor(int itemDisableColor) { + this.itemDisableColor = itemDisableColor; + setupItemsPixelMaps(); + refresh(); + } + + /** + * getItemsCount + * + * @return int + */ + public int getItemsCount() { + return items.size(); + } + + /** + * isColored + * + * @return boolean + */ + public boolean isColored() { + return colored; + } + + /** + * setColored + * + * @param colored + */ + public void setColored(boolean colored) { + this.colored = colored; + this.itemActiveColor = colored ? coloredTitleColorActive : titleColorActive; + this.itemInactiveColor = colored ? coloredTitleColorInactive : titleColorInactive; + needInit = true; + refresh(); + } + + /** + * getDefaultBackgroundColor + * + * @return int + */ + public int getDefaultBackgroundColor() { + return defaultBackgroundColor; + } + + /** + * setDefaultBackgroundColor + * + * @param defaultBackgroundColor + */ + public void setDefaultBackgroundColor(int defaultBackgroundColor) { + this.defaultBackgroundColor = defaultBackgroundColor; + invalidate(); + } + + /** + * getAccentColor + * + * @return int + */ + public int getAccentColor() { + return itemActiveColor; + } + + /** + * getAccentColor + * + * @param accentColor + */ + public void setAccentColor(int accentColor) { + this.titleColorActive = accentColor; + this.itemActiveColor = accentColor; + setupItemsPixelMaps(); + invalidate(); + } + + /** + * getInactiveColor + * + * @return int + */ + public int getInactiveColor() { + return itemInactiveColor; + } + + /** + * setInactiveColor + * + * @param inactiveColor + */ + public void setInactiveColor(int inactiveColor) { + this.titleColorInactive = inactiveColor; + this.itemInactiveColor = inactiveColor; + setupItemsPixelMaps(); + invalidate(); + } + + /** + * setColoredModeColors + * + * @param colorActive + * @param colorInactive + */ + public void setColoredModeColors(int colorActive, int colorInactive) { + this.coloredTitleColorActive = colorActive; + this.coloredTitleColorInactive = colorInactive; + setupItemsPixelMaps(); + invalidate(); + } + + /** + * setSelectedBackgroundVisible + * + * @param visible + */ + public void setSelectedBackgroundVisible(boolean visible) { + this.selectedBackgroundVisible = visible; + invalidate(); + } + + /** + * setTitleTextSize + * + * @param activeSize + * @param inactiveSize + */ + public void setTitleTextSize(int activeSize, int inactiveSize) { + this.titleActiveTextSize = activeSize; + this.titleInactiveTextSize = inactiveSize; + invalidate(); + } + + /** + * getItem + * + * @param position + * @return BottomNavigationItem + */ + public BottomNavigationItem getItem(int position) { + if (position < 0 || position > items.size() - 1) { + return null; + } + return items.get(position); + } + + /** + * getCurrentItem + * + * @return int + */ + public int getCurrentItem() { + return mPosition; + } + + /** + * setCurrentItem + * + * @param position + */ + public void setCurrentItem(int position) { + setCurrentItem(position, true); + } + + /** + * setCurrentItem + * + * @param position + * @param useCallback + */ + public void setCurrentItem(int position, boolean useCallback) { + if (position >= items.size()) { + return; + } + choose(position, useCallback); + } + + /** + * hideBottomNavigation + */ + public void hideBottomNavigation() { + hideBottomNavigation(true); + } + + /** + * hideBottomNavigation + * + * @param withAnimation + */ + public void hideBottomNavigation(boolean withAnimation) { + if (mIsHidden) { + return; + } + mIsHidden = true; + if (showAnimatorValue == null) { + showAnimatorValue = new AnimatorValue(); + } else if (showAnimatorValue.isRunning()) { + showAnimatorValue.stop(); + } + if (withAnimation) { + int start = showBottom; + int end = bottomNavigationHeight + mElevation; + showAnimatorValue.setDuration(showDurationTime * (end - start) / (bottomNavigationHeight + mElevation)); + showAnimatorValue.setValueUpdateListener( + new AnimatorValue.ValueUpdateListener() { + @Override + public void onUpdate(AnimatorValue animatorValue, float v) { + showBottom = (int) (start + (end - start) * v); + ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig(); + layoutConfig.setMarginBottom((int) (-showBottom)); + setLayoutConfig(layoutConfig); + if (navigationPositionListener != null) { + navigationPositionListener.onPositionChange(showBottom); + } + } + }); + showAnimatorValue.start(); + } else { + ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig(); + layoutConfig.setMarginBottom(-bottomNavigationHeight); + setLayoutConfig(layoutConfig); + } + } + + /** + * restoreBottomNavigation + */ + public void restoreBottomNavigation() { + restoreBottomNavigation(true); + } + + /** + * restoreBottomNavigation + * + * @param withAnimation + */ + public void restoreBottomNavigation(boolean withAnimation) { + if (!mIsHidden) { + return; + } + mIsHidden = false; + if (showAnimatorValue == null) { + showAnimatorValue = new AnimatorValue(); + } else if (showAnimatorValue.isRunning()) { + showAnimatorValue.stop(); + } + if (withAnimation) { + int start = showBottom; + int end = 0; + showAnimatorValue.setDuration(showDurationTime * (start - end) / (bottomNavigationHeight + mElevation)); + showAnimatorValue.setValueUpdateListener( + new AnimatorValue.ValueUpdateListener() { + @Override + public void onUpdate(AnimatorValue animatorValue, float v) { + showBottom = (int) (start + (end - start) * v); + ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig(); + layoutConfig.setMarginBottom(-showBottom); + setLayoutConfig(layoutConfig); + if (navigationPositionListener != null) { + navigationPositionListener.onPositionChange(showBottom); + } + } + }); + showAnimatorValue.start(); + } else { + ComponentContainer.LayoutConfig layoutConfig = getLayoutConfig(); + layoutConfig.setMarginBottom(0); + setLayoutConfig(layoutConfig); + } + } + + /** + * isForceTint + * + * @return boolean + */ + public boolean isForceTint() { + return forceTint; + } + + /** + * setForceTint + * + * @param forceTint + */ + public void setForceTint(boolean forceTint) { + this.forceTint = forceTint; + invalidate(); + } + + /** + * getTitleState + * + * @return TitleState + */ + public TitleState getTitleState() { + return titleState; + } + + /** + * setTitleState + * + * @param titleState + */ + public void setTitleState(TitleState titleState) { + this.titleState = titleState; + needInit = true; + invalidate(); + } + + /** + * setOnTabSelectedListener + * + * @param tabSelectedListener + */ + public void setOnTabSelectedListener(OnTabSelectedListener tabSelectedListener) { + this.tabSelectedListener = tabSelectedListener; + } + + /** + * removeOnTabSelectedListener + */ + public void removeOnTabSelectedListener() { + this.tabSelectedListener = null; + } + + /** + * setOnNavigationPositionListener + * + * @param navigationPositionListener + */ + public void setOnNavigationPositionListener(OnNavigationPositionListener navigationPositionListener) { + this.navigationPositionListener = navigationPositionListener; + } + + /** + * removeOnNavigationPositionListener + */ + public void removeOnNavigationPositionListener() { + this.navigationPositionListener = null; + } + + /** + * setNotification + * + * @param nbNotification + * @param itemPosition + */ + public void setNotification(int nbNotification, int itemPosition) { + if (itemPosition < 0 || itemPosition > items.size() - 1) { + return; + } + final String title = nbNotification == 0 ? "" : String.valueOf(nbNotification); + notifications.set(itemPosition, Notification.justText(title)); + invalidate(); + } + + /** + * setNotification + * + * @param notification + * @param itemPosition + */ + public void setNotification(Notification notification, int itemPosition) { + if (itemPosition < 0 || itemPosition > items.size() - 1) { + return; + } + if (notification == null) { + notification = new Notification(); // instead of null, use empty notification + } + notifications.set(itemPosition, notification); + invalidate(); + } + + /** + * setNotificationTextColor + * + * @param textColor + */ + public void setNotificationTextColor(int textColor) { + this.notificationTextColor = textColor; + invalidate(); + } + + /** + * setNotificationBackgroundColor + * + * @param color + */ + public void setNotificationBackgroundColor(int color) { + this.notificationBackgroundColor = color; + invalidate(); + } + + /** + * setNotificationMargin + * + * @param marginLeft + * @param marginTop + */ + public void setNotificationMargin(int marginLeft, int marginTop) { + this.notificationMarginLeft = marginLeft; + this.notificationMarginTop = marginTop; + invalidate(); + } + + /** + * setUseElevation + * + * @param useElevation + */ + public void setUseElevation(boolean useElevation) { + mElevation = useElevation ? dp2px(8) : 0; + invalidate(); + } + + /** + * setUseElevation + * + * @param useElevation + * @param elevation + */ + public void setUseElevation(boolean useElevation, int elevation) { + mElevation = useElevation ? elevation : 0; + invalidate(); + } + + /** + * isHidden + * + * @return boolean + */ + public boolean isHidden() { + return mIsHidden; + } + + /** + * setSelectHideNotification + * + * @param selectHideNotification + */ + public void setSelectHideNotification(boolean selectHideNotification) { + this.selectHideNotification = selectHideNotification; + } + + /** + * getSelectHideNotification + * + * @return boolean + */ + public boolean getSelectHideNotification() { + return selectHideNotification; + } + + /** + * getTitleColorActive + * + * @return + */ + public int getTitleColorActive() { + return titleColorActive; + } + + public int getTitleColorInactive() { + return titleColorInactive; + } + + public int getColoredTitleColorActive() { + return coloredTitleColorActive; + } + + public int getColoredTitleColorInactive() { + return coloredTitleColorInactive; + } + + public int getItemDisableColor() { + return itemDisableColor; + } + + public void setTouchEffect(boolean touchEffect) { + this.touchEffect = touchEffect; + } + + public boolean getTouchEffect() { + return touchEffect; + } + + private void setupItemsPixelMaps() { + if (items != null && items.size() > 0) { + for (BottomNavigationItem bottomNavigationItem : items) { + bottomNavigationItem.setupPixelMap(this, iconSize); + } + } + } + + private void getMeasurements() { + if (isClassic()) { + float minWidth = dp2px(104); + float maxWidth = dp2px(169); + + if (titleState == TitleState.ALWAYS_SHOW && items.size() > MIN_ITEMS) { + minWidth = dp2px(64); + maxWidth = dp2px(96); + } + + int layoutWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + if (layoutWidth == 0 || items.size() == 0) { + return; + } + + float itemWidth = layoutWidth / items.size(); + if (itemWidth < minWidth) { + itemWidth = minWidth; + } else if (itemWidth > maxWidth) { + itemWidth = maxWidth; + } + selectedItemWidth = itemWidth; + notSelectedItemWidth = itemWidth; + } else { + float minWidth = dp2px(64); + float maxWidth = dp2px(96); + + int layoutWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + if (layoutWidth == 0 || items.size() == 0) { + return; + } + + float itemWidth = layoutWidth / items.size(); + + if (itemWidth < minWidth) { + itemWidth = minWidth; + } else if (itemWidth > maxWidth) { + itemWidth = maxWidth; + } + + float difference = dp2px(10); + selectedItemWidth = itemWidth + items.size() * difference; + itemWidth -= difference; + notSelectedItemWidth = itemWidth; + + if (titleState == TitleState.ALWAYS_HIDE) { + selectedItemWidth = itemWidth * 1.16f; + notSelectedItemWidth = itemWidth * 1.16f; + } + } + + offsetX = (getWidth() - (selectedItemWidth + notSelectedItemWidth * (items.size() - 1))) / 2; + } + + private int getCurrentCenterX(int index, int selectIndex) { + int x = 0; + if (items != null && items.size() > 0) { + for (int i = 0; i < items.size() && i <= index; i++) { + if (index == i) { + if (selectIndex == i) { + x += selectedItemWidth / 2; + } else { + x += notSelectedItemWidth / 2; + } + } else { + if (selectIndex == i) { + x += selectedItemWidth; + } else { + x += notSelectedItemWidth; + } + } + } + } + return x; + } + + /** + * OnTabSelectedListener + */ + public interface OnTabSelectedListener { + /** + * onTabSelected + * + * @param position + * @param wasSelected + * @return boolean + */ + boolean onTabSelected(int position, boolean wasSelected); + } + + /** + * OnNavigationPositionListener + */ + public interface OnNavigationPositionListener { + /** + * onPositionChange + * + * @param y + */ + void onPositionChange(int y); + } + + private int getPostionFromX(float touchX) { + float x = offsetX; + if (items != null && items.size() > 0) { + for (int i = 0; i < items.size(); i++) { + if (mPosition == i) { + x += selectedItemWidth; + } else { + x += notSelectedItemWidth; + } + if (x >= touchX) { + return i; + } + } + } + return -1; + } + + private float getTouchX(TouchEvent touchEvent, int index) { + float x = 0; + if (touchEvent.getPointerCount() > index) { + int[] xy = getLocationOnScreen(); + if (xy != null && xy.length == 2) { + x = touchEvent.getPointerScreenPosition(index).getX() - xy[0]; + } else { + x = touchEvent.getPointerPosition(index).getX(); + } + } + return x; + } + + private float getTouchY(TouchEvent touchEvent, int index) { + float y = 0; + if (touchEvent.getPointerCount() > index) { + int[] xy = getLocationOnScreen(); + if (xy != null && xy.length == 2) { + y = touchEvent.getPointerScreenPosition(index).getY() - xy[1]; + } else { + y = touchEvent.getPointerPosition(index).getY(); + } + } + return y; + } + + /** + * dp2px + * + * @param dp + * @return + */ + protected int dp2px(float dp) { + return (int) (getResourceManager().getDeviceCapability().screenDensity / 160 * dp); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigationItem.java b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigationItem.java new file mode 100644 index 0000000000000000000000000000000000000000..a4c608200bdfac6c90bde21fcfe3cb9179860628 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/BottomNavigationItem.java @@ -0,0 +1,200 @@ +package com.wordplat.quickstart.wight.navigation; + +import ohos.agp.render.Canvas; +import ohos.agp.render.Texture; +import ohos.agp.utils.Color; +import ohos.app.Context; +import ohos.media.image.PixelMap; +import ohos.media.image.common.AlphaType; +import ohos.media.image.common.PixelFormat; +import ohos.media.image.common.Size; + +import java.util.Optional; + +/** + * BottomNavigationItem + * + * @since 2021-05-08 + */ +public class BottomNavigationItem { + private String title = ""; + private int color = Color.GRAY.getValue(); + + private PixelMap orgPixelMap; + + private PixelMap activePixelMap; + private PixelMap inActivePixelMap; + private PixelMap disablePixelMap; + private PixelMap coloredInActivePixelMap; + private PixelMap coloredActivePixelMap; + private int iconSize; + + private int drawableRes; + + private BottomNavigation bottomNavigation; + + /** + * BottomNavigationItem + * + * @param title + * @param resource + * @param context + */ + public BottomNavigationItem(String title, int resource, Context context) { + this.title = title; + this.drawableRes = resource; + Optional optional = ResUtil.getPixelMap(context, drawableRes); + orgPixelMap = optional.isPresent() ? optional.get() : null; + } + + /** + * BottomNavigationItem + * + * @param title + * @param context + */ + public BottomNavigationItem(String title, Context context) { + this.title = title; + } + + /** + * BottomNavigationItem + * + * @param title + * @param resource + * @param color + * @param context + */ + public BottomNavigationItem(String title, int resource, int color, Context context) { + this.title = title; + this.drawableRes = resource; + this.color = color; + Optional optional = ResUtil.getPixelMap(context, drawableRes); + orgPixelMap = optional.isPresent() ? optional.get() : null; + } + + /** + * BottomNavigationItem + * + * @param title + * @param pixelMap + */ + public BottomNavigationItem(String title, PixelMap pixelMap) { + this.title = title; + this.orgPixelMap = pixelMap; + } + + /** + * BottomNavigationItem + * + * @param title + * @param pixelMap + * @param color + */ + public BottomNavigationItem(String title, PixelMap pixelMap, int color) { + this.title = title; + this.orgPixelMap = pixelMap; + this.color = color; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + + PixelMap getOrgPixelMap() { + return orgPixelMap; + } + + PixelMap getActivePixelMap() { + return activePixelMap; + } + + PixelMap getInActivePixelMap() { + return inActivePixelMap; + } + + PixelMap getDisablePixelMap() { + return disablePixelMap; + } + + PixelMap getColoredInActivePixelMap() { + return coloredInActivePixelMap; + } + + PixelMap getColoredActivePixelMap() { + return coloredActivePixelMap; + } + + void setupPixelMap(BottomNavigation navigation, int iconsize) { + this.bottomNavigation = navigation; + this.iconSize = iconsize; + if (orgPixelMap != null) { + generateIconBitmaps(navigation); + } + } + + private void generateIconBitmaps(BottomNavigation bottomnavigation) { + if (orgPixelMap == null) { + return; + } + orgPixelMap = scaleIcon(orgPixelMap); + PixelMap origin = orgPixelMap; + activePixelMap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height); + Canvas canvas = new Canvas(new Texture(activePixelMap)); + canvas.drawColor(bottomnavigation.getTitleColorActive(), Canvas.PorterDuffMode.SRC_IN); + inActivePixelMap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height); + canvas = new Canvas(new Texture(inActivePixelMap)); + canvas.drawColor(bottomnavigation.getTitleColorInactive(), Canvas.PorterDuffMode.SRC_IN); + coloredActivePixelMap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height); + canvas = new Canvas(new Texture(coloredActivePixelMap)); + canvas.drawColor(bottomnavigation.getColoredTitleColorActive(), Canvas.PorterDuffMode.SRC_IN); + coloredInActivePixelMap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height); + canvas = new Canvas(new Texture(coloredInActivePixelMap)); + canvas.drawColor(bottomnavigation.getColoredTitleColorInactive(), Canvas.PorterDuffMode.SRC_IN); + disablePixelMap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height); + canvas = new Canvas(new Texture(disablePixelMap)); + canvas.drawColor(bottomnavigation.getItemDisableColor(), Canvas.PorterDuffMode.SRC_IN); + } + + private PixelMap scaleIcon(PixelMap origin) { + int width = origin.getImageInfo().size.width; + int height = origin.getImageInfo().size.height; + int size = Math.max(width, height); + if (size == iconSize) { + return origin; + } else if (size > iconSize) { + int scaledWidth; + int scaledHeight; + if (width > iconSize) { + scaledWidth = iconSize; + scaledHeight = (int) (iconSize * ((float) height / width)); + } else { + scaledHeight = iconSize; + scaledWidth = (int) (iconSize * ((float) width / height)); + } + return copy(origin, scaledWidth, scaledHeight); + } else { + return origin; + } + } + + private PixelMap copy(PixelMap source, int width, int height) { + PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions(); + initializationOptions.pixelFormat = PixelFormat.ARGB_8888; + initializationOptions.alphaType = AlphaType.PREMUL; + initializationOptions.size = new Size(width, height); + return PixelMap.create(source, initializationOptions); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/navigation/Notification.java b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/Notification.java new file mode 100644 index 0000000000000000000000000000000000000000..db2d93e0ab906b1b8c04fa9106d0839482ccc5df --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/Notification.java @@ -0,0 +1,124 @@ +package com.wordplat.quickstart.wight.navigation; + +import java.util.ArrayList; +import java.util.List; + +/** + * Notification + * + * @since 2021-05-08 + */ +public class Notification { + private String text; + + private int textColor; + + private int backgroundColor; + + /** + * Notification + */ + public Notification() { + } + + /** + * isEmpty + * + * @return boolean + */ + public boolean isEmpty() { + if (text == null || text.length() == 0) { + return true; + } else { + return false; + } + } + + public String getText() { + return text; + } + + public int getTextColor() { + return textColor; + } + + public int getBackgroundColor() { + return backgroundColor; + } + + /** + * justText + * + * @param text + * @return Notification + */ + public static Notification justText(String text) { + return new Builder().setText(text).build(); + } + + /** + * generateEmptyList + * + * @param size + * @return List + */ + public static List generateEmptyList(int size) { + List notificationList = new ArrayList<>(); + for (int i = 0; i < size; i++) { + notificationList.add(new Notification()); + } + return notificationList; + } + + public static class Builder { + private String text; + private int textColor; + private int backgroundColor; + + /** + * setText + * + * @param text + * @return Builder + */ + public Builder setText(String text) { + this.text = text; + return this; + } + + /** + * setTextColor + * + * @param textColor + * @return Builder + */ + public Builder setTextColor(int textColor) { + this.textColor = textColor; + return this; + } + + /** + * setBackgroundColor + * + * @param backgroundColor + * @return Builder + */ + public Builder setBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + /** + * build + * + * @return Notification + */ + public Notification build() { + Notification notification = new Notification(); + notification.text = text; + notification.textColor = textColor; + notification.backgroundColor = backgroundColor; + return notification; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/wight/navigation/ResUtil.java b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/ResUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..f6f4dd2ff434fa9de315547a34d02cfc6813587e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/wight/navigation/ResUtil.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.wight.navigation; + +import ohos.agp.colors.RgbColor; +import ohos.agp.components.Component; +import ohos.agp.components.element.Element; +import ohos.agp.components.element.PixelMapElement; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.components.element.VectorElement; +import ohos.agp.render.Arc; +import ohos.agp.utils.Color; +import ohos.agp.utils.Rect; +import ohos.app.Context; +import ohos.global.resource.NotExistException; +import ohos.global.resource.RawFileEntry; +import ohos.global.resource.Resource; +import ohos.global.resource.ResourceManager; +import ohos.global.resource.WrongTypeException; +import ohos.media.image.ImageSource; +import ohos.media.image.PixelMap; +import ohos.media.image.common.PixelFormat; +import ohos.media.image.common.Size; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import java.util.OptionalInt; + +/** + * get resource class + * + * @since 2021-05-11 + */ +public class ResUtil { + private static final String TAG = ResUtil.class.getSimpleName(); + + private static final String CITY_ID_ATTR = "cityId_"; + + private static final String STRING_ID_ATTR = "String_"; + + private ResUtil() { + } + + /** + * get the path from id + * + * @param context the context + * @param id the id + * @return the path from id + */ + public static String getPathById(Context context, int id) { + String path = ""; + if (context == null) { + return path; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return path; + } + try { + path = manager.getMediaPath(id); + } catch (IOException | NotExistException | WrongTypeException e) { + path = ""; + } + return path; + } + + /** + * get the new color + * + * @param context the context + * @param id the id + * @return the color + */ + public static Color getNewColor(Context context, int id) { + return new Color(getColor(context, id)); + } + + /** + * get the color + * + * @param context the context + * @param id the id + * @return the color + */ + public static int getColor(Context context, int id) { + int result = 0; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getColor(); + } catch (IOException | WrongTypeException | NotExistException e) { + result = 0; + } + return result; + } + + /** + * get the int + * + * @param context the context + * @param id the int array + * @return the int array + */ + public static int getInt(Context context, int id) { + int result = 0; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getInteger(); + } catch (IOException e) { + result = 0; + } catch (NotExistException e) { + result = 0; + } catch (WrongTypeException e) { + result = 0; + } + return result; + } + + /** + * get the int array + * + * @param context the context + * @param id the int array + * @return the int array + */ + public static int[] getIntArray(Context context, int id) { + int[] result = null; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getIntArray(); + } catch (IOException e) { + result = null; + } catch (NotExistException e) { + result = null; + } catch (WrongTypeException e) { + result = null; + } + return result; + } + + /** + * get the dimen value + * + * @param context the context + * @param id the id + * @return get the float dimen value + */ + public static float getDimen(Context context, int id) { + float result = 0; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getFloat(); + } catch (IOException | NotExistException | WrongTypeException e) { + result = 0; + } + return result; + } + + /** + * get string + * + * @param context the context + * @param id the string id + * @return string of the given id + */ + public static String getString(Context context, int id) { + String result = ""; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getString(); + } catch (IOException | NotExistException | WrongTypeException e) { + result = null; + } + return result; + } + + /** + * get boolean + * + * @param context the context + * @param id the boolean id + * @return boolean of the given id + */ + public static boolean getBoolean(Context context, int id) { + boolean result = false; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getBoolean(); + } catch (IOException | NotExistException | WrongTypeException e) { + result = false; + } + return result; + } + + /** + * get the string array + * + * @param context the context + * @param id the string array id + * @return the string array + */ + public static String[] getStringArray(Context context, int id) { + String[] result = null; + if (context == null) { + return result; + } + ResourceManager manager = context.getResourceManager(); + if (manager == null) { + return result; + } + try { + result = manager.getElement(id).getStringArray(); + } catch (IOException | NotExistException | WrongTypeException e) { + result = null; + } + return result; + } + + /** + * get the vector drawable + * + * @param context the context + * @param id the drawable id + * @return the vector drawable + */ + public static VectorElement getVectorDrawable(Context context, int id) { + if (context == null) { + return null; + } + + return new VectorElement(context, id); + } + + /** + * get the pixel map + * + * @param context the context + * @param id the id + * @return the pixel map + */ + public static Optional getPixelMap(Context context, int id) { + String path = getPathById(context, id); + if (path == null || path.length() == 0) { + return Optional.empty(); + } + RawFileEntry assetManager = context.getResourceManager().getRawFileEntry(path); + ImageSource.SourceOptions options = new ImageSource.SourceOptions(); + options.formatHint = "image/png"; + ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); + try { + Resource asset = assetManager.openRawFile(); + ImageSource source = ImageSource.create(asset, options); + return Optional.ofNullable(source.createPixelmap(decodingOptions)); + } catch (IOException e) { + Optional.empty(); + } + return Optional.empty(); + } + + /** + * get the pixel map + * + * @param context the context + * @param path the path + * @return the pixel map + */ + public static Optional getPixelMap(Context context, String path) { + RawFileEntry assetManager = context.getResourceManager().getRawFileEntry(path); + ImageSource.SourceOptions options = new ImageSource.SourceOptions(); + options.formatHint = "image/png"; + ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); + try { + Resource asset = assetManager.openRawFile(); + ImageSource source = ImageSource.create(asset, options); + return Optional.ofNullable(source.createPixelmap(decodingOptions)); + } catch (IOException e) { + Optional.empty(); + } + return Optional.empty(); + } + + /** + * get the pixel map + * + * @param context the context + * @param asset the InputStream + * @return the pixel map + */ + public static Optional getPixelMap(Context context, InputStream asset) { + ImageSource.SourceOptions options = new ImageSource.SourceOptions(); + ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); + ImageSource source = ImageSource.create(asset, options); + return Optional.ofNullable(source.createPixelmap(decodingOptions)); + } + + /** + * get the Pixel Map Element + * + * @param context the context + * @param resId the res id + * @return the Pixel Map Element + */ + public static PixelMapElement getPixelMapDrawable(Context context, int resId) { + Optional optional = getPixelMap(context, resId); + return optional.map(PixelMapElement::new).orElse(null); + } + + /** + * get the Element + * + * @param color the color + * @return the Element + */ + public static Element buildDrawableByColor(int color) { + ShapeElement drawable = new ShapeElement(); + drawable.setShape(ShapeElement.RECTANGLE); + drawable.setRgbColor(RgbColor.fromArgbInt(color)); + return drawable; + } + + /** + * get the Element By ColorRadius + * + * @param color the color + * @param radius the radius + * @return the Element By ColorRadius + */ + public static Element buildDrawableByColorRadius(int color, float radius) { + ShapeElement drawable = new ShapeElement(); + drawable.setShape(ShapeElement.RECTANGLE); + drawable.setRgbColor(RgbColor.fromArgbInt(color)); + drawable.setCornerRadius(radius); + return drawable; + } + + /** + * get the ShapeElement + * + * @param thickness the thickness + * @param inside the inside color + * @param border the border color + * @param startAngle the start angle + * @param sweepAngle the sweep angle + * @return the ShapeElement + */ + public static ShapeElement getCustomArcGradientDrawable( + int thickness, Color inside, Color border, float startAngle, float sweepAngle) { + ShapeElement drawable = new ShapeElement(); + drawable.setShape(ShapeElement.ARC); + drawable.setRgbColor(RgbColor.fromArgbInt(inside.getValue())); // keep it transparent for main(inner) part + drawable.setArc(new Arc(startAngle, sweepAngle, false)); + drawable.setStroke(thickness, RgbColor.fromArgbInt(border.getValue())); + return drawable; + } + + /** + * get the Element + * + * @param thickness the thickness + * @param inside the inside color + * @param border the border color + * @param rect the rect + * @return the Element + */ + public static Element getCustomCircleGradientDrawable(int thickness, Color inside, Color border, Rect rect) { + ShapeElement element = new ShapeElement(); + element.setShape(ShapeElement.OVAL); + element.setRgbColor(RgbColor.fromArgbInt(inside.getValue())); + element.setStroke(2, RgbColor.fromArgbInt(border.getValue())); + element.setBounds(rect); + return element; + } + + /** + * get the Element + * + * @param inside the inside color + * @param rect the rect + * @return the Element + */ + public static Element getCustomRectGradientDrawable(Color inside, Rect rect) { + ShapeElement element = new ShapeElement(); + element.setShape(ShapeElement.RECTANGLE); + element.setRgbColor(RgbColor.fromArgbInt(inside.getValue())); + element.setBounds(rect); + return element; + } + + /** + * get res id by reflect + * + * @param resClass res class + * @param resName res name + * @return res id + */ + public static OptionalInt getResIdByReflect(Class resClass, String resName) { + return null; + } + + /** + * get native city name + * + * @param context the context + * @param cityId cityId + * @return city name from cityId + */ + public static String getNativeCityName(Context context, String cityId) { + return null; + } + + /** + * find view by id + * + * @param view rootView + * @param id res id + * @param type + * @return view + */ + public static T findViewById(Component view, int id) { + if (view == null) { + return null; + } + return (T) view.findComponentById(id); + } + + /** + * get Element from Resource + * + * @param resource the resource + * @return Element + * @throws IOException IOException + * @throws NotExistException NotExistException + */ + public static PixelMapElement prepareElement(Resource resource) throws IOException, NotExistException { + return new PixelMapElement(preparePixelmap(resource)); + } + + /** + * get PixelMap from Resource + * + * @param resource the resource + * @return PixelMap + * @throws IOException IOException + * @throws NotExistException NotExistException + */ + public static PixelMap preparePixelmap(Resource resource) throws IOException, NotExistException { + ImageSource.SourceOptions srcOpts = new ImageSource.SourceOptions(); + ImageSource imageSource = null; + try { + imageSource = ImageSource.create(readResource(resource), srcOpts); + } finally { + close(resource); + } + if (imageSource == null) { + throw new FileNotFoundException(); + } + ImageSource.DecodingOptions decodingOpts = new ImageSource.DecodingOptions(); + decodingOpts.desiredSize = new Size(0, 0); + decodingOpts.desiredRegion = new ohos.media.image.common.Rect(0, 0, 0, 0); + decodingOpts.desiredPixelFormat = PixelFormat.ARGB_8888; + + PixelMap pixelmap = imageSource.createPixelmap(decodingOpts); + return pixelmap; + } + + /** + * close Resource + * + * @param resource the resource + */ + private static void close(Resource resource) { + if (resource != null) { + try { + resource.close(); + } catch (IOException e) { + return; + } + } + } + + /** + * get bytes from Resource + * + * @param resource the resource + * @return bytes + */ + private static byte[] readResource(Resource resource) { + final int bufferSize = 1024; + final int ioEnd = -1; + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + byte[] buffer = new byte[bufferSize]; + byte[] bytes = null; + try { + while (true) { + int readLen = resource.read(buffer, 0, bufferSize); + if (readLen == ioEnd) { + break; + } + output.write(buffer, 0, readLen); + } + } catch (IOException e) { + return null; + } finally { + bytes = output.toByteArray(); + try { + output.close(); + } catch (IOException e) { + bytes = null; + } + } + return bytes; + } + + /** + * get Element from ResourceId + * + * @param context the context + * @param resId the resourceId + * @return Element + */ + public static Element getDrawable(Context context, int resId) { + ResourceManager rsrc = context.getResourceManager(); + if (rsrc == null) { + return null; + } + + Element drawable = null; + if (resId != 0) { + try { + Resource resource = rsrc.getResource(resId); + drawable = (Element) prepareElement(resource); + } catch (Exception e) { + drawable = null; + } + } + return drawable; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/DbManager.java b/entry/src/main/java/com/wordplat/quickstart/xutils/DbManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2572b19959753e78e24f621f3a7b215fd09c7753 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/DbManager.java @@ -0,0 +1,481 @@ +package com.wordplat.quickstart.xutils; + +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.db.Selector; +import com.wordplat.quickstart.xutils.db.sqlite.SqlInfo; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.db.table.TableEntity; +import com.wordplat.quickstart.xutils.ex.DbException; + +import ohos.data.rdb.RdbStore; +import ohos.data.resultset.ResultSet; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * 数据库访问接口 + * + * @since 2021-05-09 + */ +public interface DbManager extends Closeable { + /** + * getDaoConfig + * + * @return DaoConfig + */ + DaoConfig getDaoConfig(); + + /** + * getDatabase + * + * @return RdbStore + */ + RdbStore getDatabase(); + + /** + * saveBindingId + * + * @param entity + * @return boolean + * @throws DbException + */ + boolean saveBindingId(Object entity) throws DbException; + + /** + * saveOrUpdate + * + * @param entity + * @throws DbException + */ + void saveOrUpdate(Object entity) throws DbException; + + /** + * save + * + * @param entity + * @throws DbException + */ + void save(Object entity) throws DbException; + + /** + * replace + * + * @param entity + * @throws DbException + */ + void replace(Object entity) throws DbException; + + /** + * deleteById + * + * @param entityType + * @param idValue + * @throws DbException + */ + void deleteById(Class entityType, Object idValue) throws DbException; + + /** + * delete + * + * @param entity + * @throws DbException + */ + void delete(Object entity) throws DbException; + + /** + * delete + * + * @param entityType + * @throws DbException + */ + void delete(Class entityType) throws DbException; + + /** + * delete + * + * @param entityType + * @param whereBuilder + * @return int + * @throws DbException + */ + int delete(Class entityType, WhereBuilder whereBuilder) throws DbException; + + /** + * update + * + * @param entity + * @param updateColumnNames + * @throws DbException + */ + void update(Object entity, String... updateColumnNames) throws DbException; + + /** + * update + * + * @param entityType + * @param whereBuilder + * @param nameValuePairs + * @return update + * @throws DbException + */ + int update(Class entityType, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException; + + /** + * findById + * + * @param entityType + * @param idValue + * @param + * @return T + * @throws DbException + */ + T findById(Class entityType, Object idValue) throws DbException; + + /** + * findFirst + * + * @param entityType + * @param + * @return T + * @throws DbException + */ + T findFirst(Class entityType) throws DbException; + + /** + * findAll + * + * @param entityType + * @param + * @return List + * @throws DbException + */ + List findAll(Class entityType) throws DbException; + + /** + * selector + * + * @param entityType + * @param + * @return Selector + * @throws DbException + */ + Selector selector(Class entityType) throws DbException; + + /** + * findDbModelFirst + * + * @param sqlInfo + * @return DbModel + * @throws DbException + */ + DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException; + + /** + * findDbModelAll + * + * @param sqlInfo + * @return List + * @throws DbException + */ + List findDbModelAll(SqlInfo sqlInfo) throws DbException; + + /** + * getTable + * + * @param entityType + * @param + * @return T + * @throws DbException + */ + TableEntity getTable(Class entityType) throws DbException; + + /** + * dropTable + * + * @param entityType + * @throws DbException + */ + void dropTable(Class entityType) throws DbException; + + /** + * addColumn + * + * @param entityType + * @param column + * @throws DbException + */ + void addColumn(Class entityType, String column) throws DbException; + /** + * dropDb + * + * @throws DbException + */ + void dropDb() throws DbException; + /** + * close + * + * @throws IOException + */ + void close() throws IOException; + + /** + * executeUpdateDelete + * + * @param sqlInfo + * @return int + * @throws DbException + */ + int executeUpdateDelete(SqlInfo sqlInfo) throws DbException; + + /** + * executeUpdateDelete + * + * @param sql + * @return int + * @throws DbException + */ + int executeUpdateDelete(String sql) throws DbException; + + /** + * execNonQuery + * + * @param sqlInfo + * @throws DbException + */ + void execNonQuery(SqlInfo sqlInfo) throws DbException; + + /** + * execNonQuery + * + * @param sql + * @throws DbException + */ + void execNonQuery(String sql) throws DbException; + + /** + * execQuery + * + * @param sqlInfo + * @return ResultSet + * @throws DbException + */ + ResultSet execQuery(SqlInfo sqlInfo) throws DbException; + + /** + * execQuery + * + * @param sql + * @return ResultSet + * @throws DbException + */ + ResultSet execQuery(String sql) throws DbException; + + /** + * DbOpenListener + * + * @since 2021-05-09 + */ + public interface DbOpenListener { + /** + * onDbOpened + * + * @param db + * @throws DbException + */ + void onDbOpened(DbManager db) throws DbException; + } + + /** + * DbUpgradeListener + * + * @since 2021-05-09 + */ + public interface DbUpgradeListener { + /** + * onUpgrade + * + * @param db + * @param oldVersion + * @param newVersion + * @throws DbException + */ + void onUpgrade(DbManager db, int oldVersion, int newVersion) throws DbException; + } + + /** + * TableCreateListener + * + * @since 2021-05-09 + */ + public interface TableCreateListener { + /** + * onTableCreated + * + * @param db + * @param table + */ + void onTableCreated(DbManager db, TableEntity table); + } + + /** + * DbOpenListener + * + * @since 2021-05-09 + */ + public static class DaoConfig { + private File dbDir; + private String dbName = "xUtils.db"; // default db name + private int dbVersion = 1; + private boolean allowTransaction = true; + private DbUpgradeListener dbUpgradeListener; + private TableCreateListener tableCreateListener; + private DbOpenListener dbOpenListener; + + /** + * DaoConfig + */ + public DaoConfig() { + } + + /*** + * DaoConfig + * + * @param dbDir + * @return DaoConfig + */ + public DaoConfig setDbDir(File dbDir) { + this.dbDir = dbDir; + return this; + } + + /** + * setDbName + * + * @param dbName + * @return DaoConfig + */ + public DaoConfig setDbName(String dbName) { + if (!TextUtils.isEmpty(dbName)) { + this.dbName = dbName; + } + return this; + } + + /** + * setDbVersion + * + * @param dbVersion + * @return DaoConfig + */ + public DaoConfig setDbVersion(int dbVersion) { + this.dbVersion = dbVersion; + return this; + } + + /** + * setAllowTransaction + * + * @param allowTransaction + * @return DaoConfig + */ + public DaoConfig setAllowTransaction(boolean allowTransaction) { + this.allowTransaction = allowTransaction; + return this; + } + + /** + * setDbOpenListener + * + * @param dbOpenListener + * @return DaoConfig + */ + public DaoConfig setDbOpenListener(DbOpenListener dbOpenListener) { + this.dbOpenListener = dbOpenListener; + return this; + } + + /** + * setDbUpgradeListener + * + * @param dbUpgradeListener + * @return DaoConfig + */ + public DaoConfig setDbUpgradeListener(DbUpgradeListener dbUpgradeListener) { + this.dbUpgradeListener = dbUpgradeListener; + return this; + } + + /** + * setTableCreateListener + * + * @param tableCreateListener + * @return DaoConfig + */ + public DaoConfig setTableCreateListener(TableCreateListener tableCreateListener) { + this.tableCreateListener = tableCreateListener; + return this; + } + + public File getDbDir() { + return dbDir; + } + + public String getDbName() { + return dbName; + } + + public int getDbVersion() { + return dbVersion; + } + + public boolean isAllowTransaction() { + return allowTransaction; + } + + public DbOpenListener getDbOpenListener() { + return dbOpenListener; + } + + public DbUpgradeListener getDbUpgradeListener() { + return dbUpgradeListener; + } + + public TableCreateListener getTableCreateListener() { + return tableCreateListener; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DaoConfig daoConfig = (DaoConfig) o; + + if (!dbName.equals(daoConfig.dbName)) { + return false; + } + return dbDir == null ? daoConfig.dbDir == null : dbDir.equals(daoConfig.dbDir); + } + + @Override + public int hashCode() { + int result = dbName.hashCode(); + result = 31 * result + (dbDir != null ? dbDir.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return String.valueOf(dbDir) + "/" + dbName; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/HttpManager.java b/entry/src/main/java/com/wordplat/quickstart/xutils/HttpManager.java new file mode 100644 index 0000000000000000000000000000000000000000..d2218fb7838c9a4f2f99618ebbe2bf3c515cc7dc --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/HttpManager.java @@ -0,0 +1,90 @@ +package com.wordplat.quickstart.xutils; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.http.HttpMethod; +import com.wordplat.quickstart.xutils.http.RequestParams; + +/** + * Created by wyouflf on 15/6/17. + * http请求接口 + * + * @since 2021-05-09 + */ +public interface HttpManager { + /** + * Cancelable + * + * @param entity + * @param callback + * @param + * @return Cancelable + */ + Callback.Cancelable get(RequestParams entity, Callback.CommonCallback callback); + + /** + * post + * + * @param entity + * @param callback + * @param + * @return Cancelable + */ + Callback.Cancelable post(RequestParams entity, Callback.CommonCallback callback); + + /** + * Cancelable + * + * @param method + * @param entity + * @param callback + * @param + * @return request + */ + Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback callback); + + /** + * getSync + * + * @param entity + * @param resultType + * @param + * @return RequestParams + * @throws Throwable + */ + T getSync(RequestParams entity, Class resultType) throws Throwable; + + /** + * postSync + * + * @param entity + * @param resultType + * @param + * @return T + * @throws Throwable + */ + T postSync(RequestParams entity, Class resultType) throws Throwable; + + /** + * requestSync + * + * @param method + * @param entity + * @param resultType + * @param + * @return T + * @throws Throwable + */ + T requestSync(HttpMethod method, RequestParams entity, Class resultType) throws Throwable; + + /** + * requestSync + * + * @param method + * @param entity + * @param callback + * @param + * @return T + * @throws Throwable + */ + T requestSync(HttpMethod method, RequestParams entity, Callback.TypedCallback callback) throws Throwable; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ImageManager.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ImageManager.java new file mode 100644 index 0000000000000000000000000000000000000000..79ff00e65a33c8796435e0e05cac170ce26d11c8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ImageManager.java @@ -0,0 +1,82 @@ +package com.wordplat.quickstart.xutils; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.image.ImageOptions; +import ohos.agp.components.Image; +import ohos.media.image.PixelMap; + +import java.io.File; + +/** + * Created by wyouflf on 15/6/17. + * 图片绑定接口 + * + * @since 2021-05-09 + */ +public interface ImageManager { + /** + * bind + * + * @param view + * @param url + */ + void bind(Image view, String url); + + /** + * bind + * + * @param view + * @param url + * @param options + */ + void bind(Image view, String url, ImageOptions options); + + /** + * bind + * + * @param view + * @param url + * @param callback + */ + void bind(Image view, String url, Callback.CommonCallback callback); + + /** + * bind + * + * @param view + * @param url + * @param options + * @param callback + */ + void bind(Image view, String url, ImageOptions options, Callback.CommonCallback callback); + + /** + * loadDrawable + * + * @param url + * @param options + * @param callback + * @return Cancelable + */ + Callback.Cancelable loadDrawable(String url, ImageOptions options, Callback.CommonCallback callback); + + /** + * loadFile + * + * @param url + * @param options + * @param callback + * @return Cancelable + */ + Callback.Cancelable loadFile(String url, ImageOptions options, Callback.CacheCallback callback); + + /** + * clearMemCache + */ + void clearMemCache(); + + /** + * clearCacheFiles + */ + void clearCacheFiles(); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ViewInjector.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ViewInjector.java new file mode 100644 index 0000000000000000000000000000000000000000..9aafebaf90f86f0df387f0bdd898064b2d3857b6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ViewInjector.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.Component; + +/** + * Created by wyouflf on 15/10/29. + * view注入接口 + * + * @since 2021-05-09 + */ +public interface ViewInjector { + /** + * inject + * + * @param view + */ + void inject(Component view); + /** + * inject + * + * @param activity + */ + void inject(AbilitySlice activity); + /** + * inject + * + * @param handler + * @param view + */ + void inject(Object handler, Component view); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheEntity.java b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..299824b4af5fe4f1cc44a64595eadbfc0f8be6ac --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheEntity.java @@ -0,0 +1,143 @@ +package com.wordplat.quickstart.xutils.cache; + + +import com.wordplat.quickstart.xutils.db.annotation.Column; +import com.wordplat.quickstart.xutils.db.annotation.Table; + +import java.util.Date; + +/** + * Created by wyouflf on 15/8/2. + * 磁盘缓存对象 + * + * @since 2021-05-08 + */ +@Table(name = "disk_cache") +public final class DiskCacheEntity { + @Column(name = "id", isId = true) + private long id; + + @Column(name = "key", property = "UNIQUE") + private String key; + + @Column(name = "path") + private String path; + + @Column(name = "textContent") + private String textContent; + + @Column(name = "bytesContent") + private byte[] bytesContent; + + // from "max-age" (since http 1.1) + @Column(name = "expires") + private long expires = Long.MAX_VALUE; + + @Column(name = "etag") + private String etag; + + @Column(name = "hits") + private long hits; + + @Column(name = "lastModify") + private Date lastModify; + + @Column(name = "lastAccess") + private long lastAccess; + + /** + * DiskCacheEntity + */ + public DiskCacheEntity() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + /** + * package + * + * @return String + */ + String getPath() { + return path; + } + + /** + * setPath + * + * @param path + */ + void setPath(String path) { + this.path = path; + } + + public String getTextContent() { + return textContent; + } + + public void setTextContent(String textContent) { + this.textContent = textContent; + } + + public byte[] getBytesContent() { + return bytesContent; + } + + public void setBytesContent(byte[] bytesContent) { + this.bytesContent = bytesContent; + } + + public long getExpires() { + return expires; + } + + public void setExpires(long expires) { + this.expires = expires; + } + + public String getEtag() { + return etag; + } + + public void setEtag(String etag) { + this.etag = etag; + } + + public long getHits() { + return hits; + } + + public void setHits(long hits) { + this.hits = hits; + } + + public Date getLastModify() { + return lastModify; + } + + public void setLastModify(Date lastModify) { + this.lastModify = lastModify; + } + + public long getLastAccess() { + return lastAccess == 0 ? System.currentTimeMillis() : lastAccess; + } + + public void setLastAccess(long lastAccess) { + this.lastAccess = lastAccess; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheFile.java b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheFile.java new file mode 100644 index 0000000000000000000000000000000000000000..c4637baabded57c2ed2e7b11dc97b4b40652b104 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/DiskCacheFile.java @@ -0,0 +1,66 @@ +package com.wordplat.quickstart.xutils.cache; + +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.ProcessLock; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * Created by wyouflf on 15/8/3. + * 磁盘缓存文件, 操作完成后必须及时调用close()方法关闭. + * + * @since 2021-05-08 + */ +public final class DiskCacheFile extends File implements Closeable { + + private final DiskCacheEntity cacheEntity; + private final ProcessLock lock; + + DiskCacheFile(String path, DiskCacheEntity cacheEntity, ProcessLock lock) { + super(path); + this.cacheEntity = cacheEntity; + this.lock = lock; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(lock); + } + + /** + * DiskCacheFile + * + * @return DiskCacheFile + * @throws IOException + */ + public DiskCacheFile commit() throws IOException { + return getDiskCache().commitDiskCacheFile(this); + } + + /** + * LruDiskCache + * + * @return LruDiskCache + */ + public LruDiskCache getDiskCache() { + String dirName = this.getParentFile().getName(); + return LruDiskCache.getDiskCache(dirName); + } + + public DiskCacheEntity getCacheEntity() { + return cacheEntity; + } + + /** + * finalize + * + * @throws Throwable + */ + @Override + protected void finalize() throws Throwable { + super.finalize(); + this.close(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruCache.java b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruCache.java new file mode 100644 index 0000000000000000000000000000000000000000..2532b7fd21a6de83bf0bbe6ba8c3b8a041d31273 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruCache.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.cache; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Static library version . Used to write apps + * that run on API levels prior to 12. When running on API level 12 or above, + * this implementation is still used; it does not try to switch to the + * framework's implementation. See the framework SDK documentation for a class + * overview. + * + * @param K + * @param V + * @since 2021-05-08 + */ +public class LruCache { + private final LinkedHashMap map; + + /** + * Size of this cache in units. Not necessarily the number of elements. + */ + private int size; + private int maxSize; + + private int putCount; + private int createCount; + private int evictionCount; + private int hitCount; + private int missCount; + + /** + * LruCache + * + * @param maxSize + * @throws IllegalArgumentException + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); + } + + /** + * resize + * + * @param maxsize + * @throws IllegalArgumentException + */ + public void resize(int maxsize) { + if (maxsize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + + synchronized (this) { + this.maxSize = maxsize; + } + trimToSize(maxsize); + } + + /** + * get + * + * @param key + * @return V + * @throws NullPointerException + */ + public final V get(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map + * may be different when create() returns. If a conflicting value was + * added to the map while create() was working, we leave that value in + * the map and release the created value. + */ + + V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + // There was a conflict so undo that last put + map.put(key, mapValue); + } else { + size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * put + * + * @param key + * @param value + * @return V + * @throws NullPointerException + */ + public final V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + putCount++; + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * trimToSize + * + * @param maxsize + * @throws IllegalStateException + */ + public void trimToSize(int maxsize) { + while (true) { + K key; + V value; + synchronized (this) { + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + + if (size <= maxsize || map.isEmpty()) { + break; + } + + Map.Entry toEvict = map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * remove + * + * @param key + * @return V + * @throws NullPointerException + */ + public final V remove(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + * entryRemoved + * + * @param evicted + * @param key + * @param oldValue + * @param newValue + */ + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { + } + + /** + * create + * + * @param key + * @return V + */ + protected V create(K key) { + return null; + } + + private int safeSizeOf(K key, V value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * sizeOf + * + * @param key + * @param value + * @return int + */ + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * evictAll + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * size + * + * @return int + */ + public synchronized final int size() { + return size; + } + + /** + * maxSize + * + * @return int + */ + public synchronized final int maxSize() { + return maxSize; + } + + /** + * hitCount + * + * @return int + */ + public synchronized final int hitCount() { + return hitCount; + } + + /** + * missCount + * + * @return int + */ + public synchronized final int missCount() { + return missCount; + } + + /** + * createCount + * + * @return int + */ + public synchronized final int createCount() { + return createCount; + } + + /** + * putCount + * + * @return int + */ + public synchronized final int putCount() { + return putCount; + } + + /** + * evictionCount + * + * @return int + */ + public synchronized final int evictionCount() { + return evictionCount; + } + + /** + * snapshot + * + * @return Map + */ + public synchronized final Map snapshot() { + return new LinkedHashMap(map); + } + + /** + * toString + * + * @return String + */ + @Override + public synchronized final String toString() { + int accesses = hitCount + missCount; + int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; + return String.format(Locale.getDefault(), + "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", + maxSize, hitCount, missCount, hitPercent); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruDiskCache.java b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruDiskCache.java new file mode 100644 index 0000000000000000000000000000000000000000..22d525c5a6675877508bdcf0225647cdcd633df5 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/cache/LruDiskCache.java @@ -0,0 +1,428 @@ +package com.wordplat.quickstart.xutils.cache; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.task.PriorityExecutor; +import com.wordplat.quickstart.xutils.common.util.*; +import com.wordplat.quickstart.xutils.config.DbConfigs; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.ex.FileLockedException; +import com.wordplat.quickstart.xutils.x; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Created by wyouflf on 15/7/23. + * 使用sqlite索引实现的LruDiskCache + * + * @since 2021-05-08 + */ +public final class LruDiskCache { + /** + * key: cacheDirName + */ + private static final HashMap DISK_CACHE_MAP = new HashMap(5); + + private static final int LIMIT_COUNT = 5000; // 限制最多5000条数据 + private static final long LIMIT_SIZE = 1024L * 1024L * 100L; // 限制最多100M文件 + + private static final int LOCK_WAIT = 1000 * 3; // 3s + private static final String CACHE_DIR_NAME = "xUtils_cache"; + private static final String TEMP_FILE_SUFFIX = ".tmp"; + + private static final long TRIM_TIME_SPAN = 1000; + private boolean available = false; + private DbManager cacheDb; + private File cacheDir; + private long diskCacheSize = LIMIT_SIZE; + private final Executor trimExecutor = new PriorityExecutor(1, true); + + private long lastTrimTime = 0L; + + /** + * LruDiskCache + * + * @param dirName + */ + private LruDiskCache(String dirName) { + try { + this.cacheDir = FileUtil.getCacheDir(dirName); + if (this.cacheDir != null && (this.cacheDir.exists() || this.cacheDir.mkdirs())) { + available = true; + } + this.cacheDb = x.getDb(DbConfigs.HTTP.getConfig()); + } catch (Throwable ex) { + available = false; + LogUtil.e(ex.getMessage(), ex); + } + deleteNoIndexFiles(); + } + + /** + * getDiskCache + * + * @param dirName + * @return LruDiskCache + */ + public synchronized static LruDiskCache getDiskCache(String dirName) { + if (TextUtils.isEmpty(dirName)) { + dirName = CACHE_DIR_NAME; + } + LruDiskCache cache = DISK_CACHE_MAP.get(dirName); + if (cache == null) { + cache = new LruDiskCache(dirName); + DISK_CACHE_MAP.put(dirName, cache); + } + return cache; + } + + /** + * setMaxSize + * + * @param maxSize + * @return LruDiskCache + */ + public LruDiskCache setMaxSize(long maxSize) { + if (maxSize > 0L) { + long diskFreeSize = FileUtil.getDiskAvailableSize(); + if (diskFreeSize > maxSize) { + diskCacheSize = maxSize; + } else { + diskCacheSize = diskFreeSize; + } + } + return this; + } + + /** + * get + * + * @param key + * @return DiskCacheEntity + */ + public DiskCacheEntity get(String key) { + if (!available || TextUtils.isEmpty(key)) { + return null; + } + + DiskCacheEntity result = null; + try { + result = this.cacheDb.selector(DiskCacheEntity.class) + .where("key", "=", key).findFirst(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + if (result != null) { + if (result.getExpires() < System.currentTimeMillis()) { + return null; + } + + final DiskCacheEntity finalResult = result; + trimExecutor.execute(new Runnable() { + @Override + public void run() { + finalResult.setHits(finalResult.getHits() + 1); + finalResult.setLastAccess(System.currentTimeMillis()); + try { + cacheDb.update(finalResult, "hits", "lastAccess"); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + return result; + } + + /** + * put + * + * @param entity + */ + public void put(DiskCacheEntity entity) { + if (!available + || entity == null + || TextUtils.isEmpty(entity.getTextContent()) + || entity.getExpires() < System.currentTimeMillis()) { + return; + } + + try { + cacheDb.replace(entity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } + + /** + * getDiskCacheFile + * + * @param key + * @return DiskCacheFile + * @throws InterruptedException + */ + public DiskCacheFile getDiskCacheFile(String key) throws InterruptedException { + if (!available || TextUtils.isEmpty(key)) { + return null; + } + + DiskCacheFile result = null; + DiskCacheEntity entity = get(key); + if (entity != null && new File(entity.getPath()).exists()) { + ProcessLock processLock = ProcessLock.tryLock(entity.getPath(), false, LOCK_WAIT); + if (processLock != null && processLock.isValid()) { + result = new DiskCacheFile(entity.getPath(), entity, processLock); + if (!result.exists()) { + try { + cacheDb.delete(entity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + result = null; + } + } + } + + return result; + } + + /** + * createDiskCacheFile + * + * @param entity + * @return DiskCacheFile + * @throws FileLockedException + * @throws IOException + */ + public DiskCacheFile createDiskCacheFile(DiskCacheEntity entity) throws IOException { + if (!available || entity == null) { + return null; + } + + DiskCacheFile result = null; + + entity.setPath(new File(this.cacheDir, MD5.md5(entity.getKey())).getCanonicalPath()); + String tempFilePath = entity.getPath() + TEMP_FILE_SUFFIX; + ProcessLock processLock = ProcessLock.tryLock(tempFilePath, true); + if (processLock != null && processLock.isValid()) { + result = new DiskCacheFile(tempFilePath, entity, processLock); + if (!result.getParentFile().exists()) { + result.mkdirs(); + } + } else { + throw new FileLockedException(entity.getPath()); + } + + return result; + } + + /** + * clearCacheFiles + */ + public void clearCacheFiles() { + IOUtil.deleteFileOrDir(cacheDir); + } + + DiskCacheFile commitDiskCacheFile(DiskCacheFile cacheFile) throws IOException { + if (!available || cacheFile == null) { + return cacheFile; + } + + DiskCacheFile result = null; + DiskCacheEntity cacheEntity = cacheFile.getCacheEntity(); + if (cacheFile.getName().endsWith(TEMP_FILE_SUFFIX)) { // is temp file + ProcessLock processLock = null; + DiskCacheFile destFile = null; + try { + String destPath = cacheEntity.getPath(); + processLock = ProcessLock.tryLock(destPath, true, LOCK_WAIT); + if (processLock != null && processLock.isValid()) { // lock + destFile = new DiskCacheFile(destPath, cacheEntity, processLock); + if (cacheFile.renameTo(destFile)) { + try { + result = destFile; + cacheDb.replace(cacheEntity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } else { + throw new IOException("rename:" + cacheFile.getAbsolutePath()); + } + } else { + throw new FileLockedException(destPath); + } + } catch (InterruptedException ex) { + result = cacheFile; + LogUtil.e(ex.getMessage(), ex); + } finally { + if (result == null) { + result = cacheFile; + IOUtil.closeQuietly(destFile); + IOUtil.closeQuietly(processLock); + IOUtil.deleteFileOrDir(destFile); + } else { + IOUtil.closeQuietly(cacheFile); + IOUtil.deleteFileOrDir(cacheFile); + } + } + } else { + result = cacheFile; + } + + return result; + } + + /** + * trimSize + */ + private void trimSize() { + trimExecutor.execute(() -> { + if (!available) { + return; + } + + long current = System.currentTimeMillis(); + if (current - lastTrimTime < TRIM_TIME_SPAN) { + return; + } else { + lastTrimTime = current; + } + deleteExpiry(); + + try { + int count = (int) cacheDb.selector(DiskCacheEntity.class).count(); + if (count > LIMIT_COUNT + 10) { + List rmList = cacheDb.selector(DiskCacheEntity.class) + .orderBy("lastAccess").orderBy("hits") + .limit(count - LIMIT_COUNT).offset(0).findAll(); + if (rmList != null && rmList.size() > 0) { + for (DiskCacheEntity entity : rmList) { + try { + cacheDb.delete(entity); + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + deleteFileWithLock(path + TEMP_FILE_SUFFIX); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + // trim disk + try { + while (FileUtil.getFileOrDirSize(cacheDir) > diskCacheSize) { + List rmList = cacheDb.selector(DiskCacheEntity.class) + .orderBy("lastAccess").orderBy("hits").limit(10).offset(0).findAll(); + if (rmList != null && rmList.size() > 0) { + // delete cache files + for (DiskCacheEntity entity : rmList) { + try { + // delete db entity + cacheDb.delete(entity); + // delete cache files + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + deleteFileWithLock(path + TEMP_FILE_SUFFIX); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + }); + } + + private void deleteExpiry() { + if (!available) { + return; + } + + try { + WhereBuilder whereBuilder = WhereBuilder.b("expires", "<", System.currentTimeMillis()); + List rmList = cacheDb.selector(DiskCacheEntity.class).where(whereBuilder).findAll(); + // delete db entities + cacheDb.delete(DiskCacheEntity.class, whereBuilder); + if (rmList != null && rmList.size() > 0) { + // delete cache files + for (DiskCacheEntity entity : rmList) { + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + /** + * 清理未被数据库索引的历史缓存文件 + */ + private void deleteNoIndexFiles() { + trimExecutor.execute(new Runnable() { + @Override + public void run() { + if (!available) { + return; + } + + try { + File[] fileList = cacheDir.listFiles(); + if (fileList != null) { + for (File file : fileList) { + try { + long count = cacheDb.selector(DiskCacheEntity.class) + .where("path", "=", file.getCanonicalFile()).count(); + if (count < 1) { + IOUtil.deleteFileOrDir(file); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + private boolean deleteFileWithLock(String path) { + ProcessLock processLock = null; + try { + processLock = ProcessLock.tryLock(path, true); + if (processLock != null && processLock.isValid()) { + /** + * lock + */ + File file = new File(path); + return IOUtil.deleteFileOrDir(file); + } + } finally { + IOUtil.closeQuietly(processLock); + } + return false; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/Callback.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/Callback.java new file mode 100644 index 0000000000000000000000000000000000000000..1cd9aed4fa5382d861809282277f3695063dccbd --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/Callback.java @@ -0,0 +1,173 @@ +package com.wordplat.quickstart.xutils.common; + +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/6/5. + * 通用回调接口 + * + * @since 2021-05-09 + */ +public interface Callback { + + public interface CommonCallback extends Callback { + /** + * onSuccess + * + * @param result + */ + void onSuccess(ResultType result); + + /** + * onError + * + * @param ex + * @param isOnCallback + */ + void onError(Throwable ex, boolean isOnCallback); + + /** + * onCancelled + * + * @param cex + */ + void onCancelled(CancelledException cex); + + /** + * onFinished + */ + void onFinished(); + } + + public interface TypedCallback extends CommonCallback { + /** + * getLoadType + * + * @return Type + */ + Type getLoadType(); + } + + public interface CacheCallback extends CommonCallback { + /** + * onCache + * + * @param result + * @return boolean + */ + boolean onCache(ResultType result); + } + + public interface ProxyCacheCallback extends CacheCallback { + /** + * onlyCache + * + * @return boolean + */ + boolean onlyCache(); + } + + public interface PrepareCallback extends CommonCallback { + /** + * prepare + * + * @param rawData + * @return ResultType + * @throws Throwable + */ + ResultType prepare(PrepareType rawData) throws Throwable; + } + + public interface ProgressCallback extends CommonCallback { + /** + * onWaiting + */ + void onWaiting(); + + /** + * onStarted + */ + void onStarted(); + + /** + * onLoading + * + * @param total + * @param current + * @param isDownloading + */ + void onLoading(long total, long current, boolean isDownloading); + } + + public interface GroupCallback extends Callback { + /** + * onSuccess + * + * @param item + */ + void onSuccess(ItemType item); + + /** + * onError + * + * @param item + * @param ex + * @param isOnCallback + */ + void onError(ItemType item, Throwable ex, boolean isOnCallback); + + /** + * onCancelled + * + * @param item + * @param cex + */ + void onCancelled(ItemType item, CancelledException cex); + + /** + * onFinished + * + * @param item + */ + void onFinished(ItemType item); + + /** + * onAllFinished + */ + void onAllFinished(); + } + + public interface Callable { + /** + * call + * + * @param result + */ + void call(ResultType result); + } + + /** + * Cancelable + * + * @since 2021-05-09 + */ + public interface Cancelable { + /** + * cancel + */ + void cancel(); + + /** + * isCancelled + * + * @return boolean + */ + boolean isCancelled(); + } + + static class CancelledException extends RuntimeException { + public CancelledException(String detailMessage) { + super(detailMessage); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/TaskController.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/TaskController.java new file mode 100644 index 0000000000000000000000000000000000000000..9d0bf571b37f129e8909333a2c46301c5c0ba342 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/TaskController.java @@ -0,0 +1,76 @@ +package com.wordplat.quickstart.xutils.common; + +import com.wordplat.quickstart.xutils.common.task.AbsTask; + +/** + * Created by wyouflf on 15/6/11. + * 任务管理接口 + * + * @since 2021-05-09 + */ +public interface TaskController { + /** + * autoPost + * + * @param runnable + */ + void autoPost(Runnable runnable); + + /** + * post + * + * @param runnable + */ + void post(Runnable runnable); + + /** + * postDelayed + * + * @param runnable + * @param delayMillis + */ + void postDelayed(Runnable runnable, long delayMillis); + + /** + * run + * + * @param runnable + */ + void run(Runnable runnable); + + /** + * removeCallbacks + * + * @param runnable + */ + void removeCallbacks(Runnable runnable); + + /** + * start + * + * @param task + * @param + * @return AbsTask + */ + AbsTask start(AbsTask task); + + /** + * startSync + * + * @param task + * @param + * @return T + * @throws Throwable + */ + T startSync(AbsTask task) throws Throwable; + + /** + * startTasks + * + * @param groupCallback + * @param tasks + * @param + * @return Callback.Cancelable + */ + > Callback.Cancelable startTasks(Callback.GroupCallback groupCallback, T... tasks); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/AbsTask.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/AbsTask.java new file mode 100644 index 0000000000000000000000000000000000000000..a35dce1fb7419bf76b0d3b1106f3c58eae3ea8c2 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/AbsTask.java @@ -0,0 +1,252 @@ +package com.wordplat.quickstart.xutils.common.task; + + +import com.wordplat.quickstart.xutils.common.Callback; + +import ohos.eventhandler.EventRunner; + +import java.util.concurrent.Executor; + + +/** + * Created by wyouflf on 15/6/5. + * 异步任务基类 + * + * @param 任务返回值类型 + * @since 2021-05-08 + */ +public abstract class AbsTask implements Callback.Cancelable { + + private TaskProxy taskProxy = null; + private final Callback.Cancelable cancelHandler; + + private volatile boolean isCancelled = false; + private volatile State state = State.IDLE; + private ResultType result; + + /** + * AbsTask + */ + public AbsTask() { + this(null); + } + + /** + * AbsTask + * + * @param cancelHandler + */ + public AbsTask(Callback.Cancelable cancelHandler) { + this.cancelHandler = cancelHandler; + } + + /** + * doBackground + * + * @return ResultType + * @throws Throwable + */ + protected abstract ResultType doBackground() throws Throwable; + + /** + * onSuccess + * + * @param result + */ + protected abstract void onSuccess(ResultType result); + + /** + * onError + * + * @param ex + * @param isCallbackError + */ + protected abstract void onError(Throwable ex, boolean isCallbackError); + + /** + * onWaiting + */ + protected void onWaiting() { + } + + /** + * onStarted + */ + protected void onStarted() { + } + + /** + * onUpdate + * + * @param flag + * @param args + */ + protected void onUpdate(int flag, Object... args) { + } + + /** + * onCancelled + * + * @param cex + */ + protected void onCancelled(Callback.CancelledException cex) { + } + + /** + * onFinished + */ + protected void onFinished() { + } + + /** + * getPriority + * + * @return Priority + */ + public Priority getPriority() { + return null; + } + + public Executor getExecutor() { + return null; + } + + /** + * EventRunner + * + * @return EventRunner + */ + public EventRunner customLooper() { + return null; + } + + /** + * update + * + * @param flag + * @param args + */ + protected final void update(int flag, Object... args) { + if (taskProxy != null) { + taskProxy.onUpdate(flag, args); + } + } + + /** + * invoked via cancel() + */ + protected void cancelWorks() { + } + + /** + * 取消任务时是否不等待任务彻底结束, 立即收到取消的通知. + * + * @return 是否立即响应取消回调 + */ + protected boolean isCancelFast() { + return false; + } + + @Override + public final void cancel() { + /** + * LogUtil.e("图片请求abstask==="+isCancelled); + */ + if (this.isCancelled) { + return; + } + synchronized (this) { + if (this.isCancelled) { + return; + } + /** + * LogUtil.e("图片请求abstask1111==="+isCancelled); + */ + this.isCancelled = true; + cancelWorks(); + if (cancelHandler != null && !cancelHandler.isCancelled()) { + cancelHandler.cancel(); + } + if (this.state == State.WAITING || (this.state == State.STARTED && isCancelFast())) { + if (taskProxy != null) { + taskProxy.onCancelled(new Callback.CancelledException("cancelled by user")); + taskProxy.onFinished(); + } else if (this instanceof TaskProxy) { + this.onCancelled(new Callback.CancelledException("cancelled by user")); + this.onFinished(); + } + } + } + } + + @Override + public final boolean isCancelled() { + return isCancelled || state == State.CANCELLED + || (cancelHandler != null && cancelHandler.isCancelled()); + } + + public final boolean isFinished() { + return this.state.value() > State.STARTED.value(); + } + + public final State getState() { + return state; + } + + public final ResultType getResult() { + return result; + } + + /** + * package + * + * @param state + */ + void setState(State state) { + this.state = state; + } + + /** + * package + * + * @param taskProxy + */ + final void setTaskProxy(TaskProxy taskProxy) { + this.taskProxy = taskProxy; + } + + /** + * package + * + * @param result + */ + final void setResult(ResultType result) { + this.result = result; + } + + /** + * State + * + * @since 2021-04-22 + */ + public enum State { + /** + * IDLE WAITING STARTED SUCCESS CANCELLED ERROR + */ + IDLE(0), WAITING(1), STARTED(2), SUCCESS(3), CANCELLED(4), ERROR(5); + private final int value; + + private State(int value) { + this.value = value; + } + + /** + * value + * + * @return int + */ + public int value() { + return value; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/Priority.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/Priority.java new file mode 100644 index 0000000000000000000000000000000000000000..d63e19575dff15942c47b5bb4592c2f1a79dcf9a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/Priority.java @@ -0,0 +1,14 @@ +package com.wordplat.quickstart.xutils.common.task; + +/** + * Created by wyouflf on 15/6/5. + * 任务的优先级 + * + * @since 2021-05-08 + */ +public enum Priority { + /** + * UI_TOP UI_NORMAL UI_LOW DEFAULT BG_TOP BG_NORMAL BG_LOW + */ + UI_TOP, UI_NORMAL, UI_LOW, DEFAULT, BG_TOP, BG_NORMAL, BG_LOW; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityExecutor.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..2e720cef0555bbf4c2a9a6f7d8eabeb23e5bd38b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityExecutor.java @@ -0,0 +1,116 @@ +package com.wordplat.quickstart.xutils.common.task; + +import java.util.Comparator; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by wyouflf on 15/6/5. + * 支持优先级的线程池管理类 + * + * @since 2021-05-08 + */ +public class PriorityExecutor implements Executor { + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 256; + private static final int KEEP_ALIVE = 1; + private static final AtomicLong SEQ_SEED = new AtomicLong(0); + + private static final ThreadFactory FACTORY = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable runnable) { + return new Thread(runnable, "xTID#" + mCount.getAndIncrement()); + } + }; + + private static final Comparator FIFO_CMP = new Comparator() { + @Override + public int compare(Runnable lhs, Runnable rhs) { + if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) { + PriorityRunnable lpr = ((PriorityRunnable) lhs); + PriorityRunnable rpr = ((PriorityRunnable) rhs); + int result = lpr.priority.ordinal() - rpr.priority.ordinal(); + return result == 0 ? (int) (lpr.SEQ - rpr.SEQ) : result; + } else { + return 0; + } + } + }; + + private static final Comparator FILO_CMP = new Comparator() { + @Override + public int compare(Runnable lhs, Runnable rhs) { + if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) { + PriorityRunnable lpr = ((PriorityRunnable) lhs); + PriorityRunnable rpr = ((PriorityRunnable) rhs); + int result = lpr.priority.ordinal() - rpr.priority.ordinal(); + return result == 0 ? (int) (rpr.SEQ - lpr.SEQ) : result; + } else { + return 0; + } + } + }; + + private final ThreadPoolExecutor mThreadPoolExecutor; + + /** + * 默认工作线程数5 + * + * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务. + */ + public PriorityExecutor(boolean fifo) { + this(CORE_POOL_SIZE, fifo); + } + + /** + * PriorityExecutor + * + * @param poolSize 工作线程数 + * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务. + */ + public PriorityExecutor(int poolSize, boolean fifo) { + BlockingQueue mPoolWorkQueue = + new PriorityBlockingQueue(MAXIMUM_POOL_SIZE, fifo ? FIFO_CMP : FILO_CMP); + mThreadPoolExecutor = new ThreadPoolExecutor( + poolSize, + MAXIMUM_POOL_SIZE, + KEEP_ALIVE, + TimeUnit.SECONDS, + mPoolWorkQueue, + FACTORY); + } + + public int getPoolSize() { + return mThreadPoolExecutor.getCorePoolSize(); + } + + /** + * setPoolSize + * + * @param poolSize + */ + public void setPoolSize(int poolSize) { + if (poolSize > 0) { + mThreadPoolExecutor.setCorePoolSize(poolSize); + } + } + + public ThreadPoolExecutor getThreadPoolExecutor() { + return mThreadPoolExecutor; + } + + public boolean isBusy() { + return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize(); + } + + @Override + public void execute(Runnable runnable) { + if (runnable instanceof PriorityRunnable) { + ((PriorityRunnable) runnable).SEQ = SEQ_SEED.getAndIncrement(); + } + mThreadPoolExecutor.execute(runnable); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityRunnable.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityRunnable.java new file mode 100644 index 0000000000000000000000000000000000000000..3bddb0481e311135724248d14b5817dc1147b783 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/PriorityRunnable.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.common.task; + +/** + * Created by wyouflf on 15/6/5. + * 带有优先级的Runnable类型(仅在task包内可用) + */ +class PriorityRunnable implements Runnable { + + long SEQ; + + private final Runnable runnable; + public final Priority priority; + + /** + * PriorityRunnable + * + * @param priority + * @param runnable + */ + public PriorityRunnable(Priority priority, Runnable runnable) { + this.priority = priority == null ? Priority.DEFAULT : priority; + this.runnable = runnable; + } + + @Override + public final void run() { + this.runnable.run(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskControllerImpl.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5c4b0fa9c0f43559e6bc36f9db62b27392d50795 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskControllerImpl.java @@ -0,0 +1,255 @@ +package com.wordplat.quickstart.xutils.common.task; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.TaskController; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.x; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/6/5. + * 异步任务的管理类 + */ +public final class TaskControllerImpl implements TaskController { + private static volatile TaskController instance; + + private TaskControllerImpl() { + } + + /** + * registerInstance + */ + public static void registerInstance() { + if (instance == null) { + synchronized (TaskController.class) { + if (instance == null) { + instance = new TaskControllerImpl(); + } + } + } + x.Ext.setTaskController(instance); + } + + @Override + public AbsTask start(AbsTask task) { + TaskProxy proxy = null; + if (task instanceof TaskProxy) { + proxy = (TaskProxy) task; + } else { + proxy = new TaskProxy(task); + } + try { + proxy.doBackground(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return proxy; + } + + @Override + public T startSync(AbsTask task) throws Throwable { + T result = null; + try { + task.onWaiting(); + task.onStarted(); + result = task.doBackground(); + task.onSuccess(result); + } catch (Callback.CancelledException cex) { + task.onCancelled(cex); + } catch (Throwable ex) { + task.onError(ex, false); + throw ex; + } finally { + task.onFinished(); + } + return result; + } + + @Override + @SuppressWarnings("unchecked") + public > Callback.Cancelable startTasks( + final Callback.GroupCallback groupCallback, final T... tasks) { + + if (tasks == null) { + throw new IllegalArgumentException("task must not be null"); + } + + final Runnable callIfOnAllFinished = new Runnable() { + private final int total = tasks.length; + private final AtomicInteger count = new AtomicInteger(0); + + @Override + public void run() { + if (count.incrementAndGet() == total) { + if (groupCallback != null) { + try { + groupCallback.onAllFinished(); + } catch (Throwable ex) { + try { + groupCallback.onError(null, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + } + }; + + for (final T task : tasks) { + start(new TaskProxy(task) { + @Override + protected void onSuccess(Object result) { + super.onSuccess(result); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onSuccess(task); + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + + @Override + protected void onCancelled(final Callback.CancelledException cex) { + super.onCancelled(cex); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onCancelled(task, cex); + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + + @Override + protected void onError(final Throwable ex, final boolean isCallbackError) { + super.onError(ex, isCallbackError); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onError(task, ex, isCallbackError); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + }); + } + + @Override + protected void onFinished() { + super.onFinished(); + post(new Runnable() { + @Override + public void run() { + try { + if (groupCallback != null) { + groupCallback.onFinished(task); + } + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } finally { + callIfOnAllFinished.run(); + } + } + }); + } + }); + } + + return new Callback.Cancelable() { + @Override + public void cancel() { + LogUtil.e("图片请求taskController imple==="); + for (T task : tasks) { + task.cancel(); + } + } + + @Override + public boolean isCancelled() { + boolean isCancelled = true; + for (T task : tasks) { + if (!task.isCancelled()) { + isCancelled = false; + } + } + return isCancelled; + } + }; + } + + @Override + public void autoPost(Runnable runnable) { + if (runnable == null) { + return; + } + /** + * // if (Thread.currentThread()==) { + * // runnable.run(); + * // } else { + */ + TaskProxy.INTERNAL_HANDLER.postTask(runnable); + /** + * } + */ + } + + @Override + public void post(Runnable runnable) { + if (runnable == null) { + return; + } + TaskProxy.INTERNAL_HANDLER.postTask(runnable); + } + + @Override + public void postDelayed(Runnable runnable, long delayMillis) { + if (runnable == null) { + return; + } + TaskProxy.INTERNAL_HANDLER.postTask(runnable, delayMillis); + } + + @Override + public void run(Runnable runnable) { + if (!TaskProxy.PRIORITY_EXECUTOR.isBusy()) { + TaskProxy.PRIORITY_EXECUTOR.execute(runnable); + } else { + new Thread(runnable).start(); + } + } + + @Override + public void removeCallbacks(Runnable runnable) { + /** + * TaskProxy.sHandler.removeCallbacks(runnable); + */ + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskProxy.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskProxy.java new file mode 100644 index 0000000000000000000000000000000000000000..87962a29a5bb1a6c51fd7570686e6f15a555ebab --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/task/TaskProxy.java @@ -0,0 +1,304 @@ +package com.wordplat.quickstart.xutils.common.task; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.eventhandler.InnerEvent; + +import com.wordplat.quickstart.xutils.x; + +import java.util.concurrent.Executor; + +/** + * 异步任务的代理类(仅在task包内可用) + * + * @param + */ +class TaskProxy extends AbsTask { + private final static int MSG_WHAT_BASE = 1000000000; + private final static int MSG_WHAT_ON_WAITING = MSG_WHAT_BASE + 1; + private final static int MSG_WHAT_ON_START = MSG_WHAT_BASE + 2; + private final static int MSG_WHAT_ON_SUCCESS = MSG_WHAT_BASE + 3; + private final static int MSG_WHAT_ON_ERROR = MSG_WHAT_BASE + 4; + private final static int MSG_WHAT_ON_UPDATE = MSG_WHAT_BASE + 5; + private final static int MSG_WHAT_ON_CANCEL = MSG_WHAT_BASE + 6; + private final static int MSG_WHAT_ON_FINISHED = MSG_WHAT_BASE + 7; + static final InternalHandler INTERNAL_HANDLER = new InternalHandler(); + static final PriorityExecutor PRIORITY_EXECUTOR = new PriorityExecutor(true); + + private final AbsTask task; + private final Executor executor; + private final EventHandler handler; + private volatile boolean callOnCanceled = false; + private volatile boolean callOnFinished = false; + + TaskProxy(AbsTask task) { + super(task); + this.task = task; + this.task.setTaskProxy(this); + this.setTaskProxy(null); + + // set handler + EventRunner looper = task.customLooper(); + if (looper != null) { + handler = new InternalHandler(looper); + } else { + handler = INTERNAL_HANDLER; + } + + // set executor + Executor taskExecutor = task.getExecutor(); + if (taskExecutor == null) { + taskExecutor = PRIORITY_EXECUTOR; + } + this.executor = taskExecutor; + } + + @Override + protected final ResultType doBackground() throws Throwable { + this.onWaiting(); + PriorityRunnable runnable = new PriorityRunnable( + task.getPriority(), + new Runnable() { + @Override + public void run() { + try { + // 等待过程中取消 + if (callOnCanceled || TaskProxy.this.isCancelled()) { + throw new Callback.CancelledException(""); + } + + // start running + TaskProxy.this.onStarted(); + + if (TaskProxy.this.isCancelled()) { // 开始时取消 + throw new Callback.CancelledException(""); + } + + // 执行task, 得到结果. + task.setResult(task.doBackground()); + TaskProxy.this.setResult(task.getResult()); + + // 未在doBackground过程中取消成功 + if (TaskProxy.this.isCancelled()) { + throw new Callback.CancelledException(""); + } + + // 执行成功 + TaskProxy.this.onSuccess(task.getResult()); + } catch (Callback.CancelledException cex) { + TaskProxy.this.onCancelled(cex); + } catch (Throwable ex) { + TaskProxy.this.onError(ex, false); + } finally { + TaskProxy.this.onFinished(); + } + } + }); + this.executor.execute(runnable); + return null; + } + + @Override + protected void onWaiting() { + this.setState(State.WAITING); + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_WAITING, new ArgsObj(this)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_WAITING, this).sendToTarget(); + */ + } + + @Override + protected void onStarted() { + this.setState(State.STARTED); + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_START, new ArgsObj(this)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_START, this).sendToTarget(); + */ + } + + @Override + protected void onSuccess(ResultType result) { + this.setState(State.SUCCESS); + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_SUCCESS, new ArgsObj(this)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_SUCCESS, this).sendToTarget(); + */ + } + + @Override + protected void onError(Throwable ex, boolean isCallbackError) { + this.setState(State.ERROR); + /** + * handler.sendEvent(MSG_WHAT_ON_ERROR); + */ + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_ERROR, new ArgsObj(this, ex)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_ERROR, new ArgsObj(this, ex)).sendToTarget(); + */ + } + + @Override + protected void onUpdate(int flag, Object... args) { + /** + * obtainMessage(int what, int arg1, int arg2, Object obj), arg2 not be used. + * handler.sendEvent(MSG_WHAT_ON_UPDATE); + */ + + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_UPDATE, new ArgsObj(this, args)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_UPDATE, flag, flag, new ArgsObj(this, args)).sendToTarget(); + */ + } + + @Override + protected void onCancelled(Callback.CancelledException cex) { + this.setState(State.CANCELLED); + /** + * handler.sendEvent(MSG_WHAT_ON_CANCEL); + */ + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_CANCEL, new ArgsObj(this, cex)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_CANCEL, new ArgsObj(this, cex)).sendToTarget(); + */ + } + + @Override + protected void onFinished() { + /** + * handler.sendEvent(MSG_WHAT_ON_FINISHED); + */ + InnerEvent innerEvent = InnerEvent.get(MSG_WHAT_ON_FINISHED, new ArgsObj(this, this)); + handler.sendEvent(innerEvent); + /** + * handler.obtainMessage(MSG_WHAT_ON_FINISHED, this).sendToTarget(); + */ + } + + @Override + final void setState(State state) { + super.setState(state); + this.task.setState(state); + } + + @Override + public final Priority getPriority() { + return task.getPriority(); + } + + @Override + public final Executor getExecutor() { + return this.executor; + } + + /** + * ########################### inner type ############################# + */ + private static class ArgsObj { + final TaskProxy taskProxy; + final Object[] args; + + /** + * ArgsObj + * + * @param taskProxy + * @param args + */ + public ArgsObj(TaskProxy taskProxy, Object... args) { + this.taskProxy = taskProxy; + this.args = args; + } + } + + final static class InternalHandler extends EventHandler { + TaskProxy taskProxy = null; + Object[] args = null; + + public InternalHandler() { + super(EventRunner.create()); + } + + public InternalHandler(EventRunner runner) throws IllegalArgumentException { + super(runner); + } + + @Override + protected void processEvent(InnerEvent event) { + super.processEvent(event); + LogUtil.e("网络请求接收==" + event + "," + event.eventId); + if (event.object instanceof TaskProxy) { + taskProxy = (TaskProxy) event.object; + } else if (event.object instanceof ArgsObj) { + ArgsObj argsObj = (ArgsObj) event.object; + taskProxy = argsObj.taskProxy; + args = argsObj.args; + } + if (taskProxy == null) { + throw new RuntimeException("msg.obj not instanceof TaskProxy"); + } + + try { + switch (event.eventId) { + case MSG_WHAT_ON_WAITING: { + taskProxy.task.onWaiting(); + break; + } + case MSG_WHAT_ON_START: { + taskProxy.task.onStarted(); + break; + } + case MSG_WHAT_ON_SUCCESS: { + taskProxy.task.onSuccess(taskProxy.getResult()); + break; + } + case MSG_WHAT_ON_ERROR: { + assert args != null; + Throwable throwable = (Throwable) args[0]; + LogUtil.d(throwable.getMessage(), throwable); + taskProxy.task.onError(throwable, false); + break; + } + case MSG_WHAT_ON_UPDATE: { + taskProxy.task.onUpdate((int) event.param, args); + break; + } + case MSG_WHAT_ON_CANCEL: { + if (taskProxy.callOnCanceled) { + return; + } + taskProxy.callOnCanceled = true; + assert args != null; + taskProxy.task.onCancelled((Callback.CancelledException) args[0]); + break; + } + case MSG_WHAT_ON_FINISHED: { + if (taskProxy.callOnFinished) { + return; + } + taskProxy.callOnFinished = true; + taskProxy.task.onFinished(); + break; + } + default: { + break; + } + } + } catch (Throwable ex) { + taskProxy.setState(State.ERROR); + if (event.eventId != MSG_WHAT_ON_ERROR) { + taskProxy.task.onError(ex, true); + } else if (x.isDebug()) { + throw new RuntimeException(ex); + } + } + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DensityUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DensityUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d8b7e4c7e06d7eb17f250cf16a1366fabf2a9e3f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DensityUtil.java @@ -0,0 +1,47 @@ +package com.wordplat.quickstart.xutils.common.util; + +/** + * DensityUtil + * + * @since 2021-05-08 + */ +public final class DensityUtil { + private static float density = -1F; + private static int widthPixels = -1; + private static int heightPixels = -1; + + private DensityUtil() { + } + +/** + * // public static float getDensity() { + * // if (density <= 0F) { + * // density = x.app().getResources().getDisplayMetrics().density; + * // } + * // return density; + * // } + * // + * // public static int dip2px(float dpValue) { + * // return (int) (dpValue * getDensity() + 0.5F); + * // } + * // + * // public static int px2dip(float pxValue) { + * // return (int) (pxValue / getDensity() + 0.5F); + * // } + * // + * // public static int getScreenWidth() { + * // if (widthPixels <= 0) { + * // widthPixels = x.app().getResources().getDisplayMetrics().widthPixels; + * // } + * // return widthPixels; + * // } + * // + * // + * // public static int getScreenHeight() { + * // if (heightPixels <= 0) { + * // heightPixels = x.app().getResources().getDisplayMetrics().heightPixels; + * // } + * // return heightPixels; + * // } + */ +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DoubleKeyValueMap.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DoubleKeyValueMap.java new file mode 100644 index 0000000000000000000000000000000000000000..edc6547e787f3330e46e382f6f5f3f2faef24282 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/DoubleKeyValueMap.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.common.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created with IntelliJ IDEA. + * User: wyouflf + * Date: 13-6-19 + * Time: PM 1:18 + * + * @param k1 + * @param k2 + * @param v + * @since 2021-05-08 + */ +public class DoubleKeyValueMap { + private final ConcurrentHashMap> k1ConcurrentHashMapConcurrentHashMap; + + /** + * DoubleKeyValueMap + */ + public DoubleKeyValueMap() { + this.k1ConcurrentHashMapConcurrentHashMap = new ConcurrentHashMap>(); + } + + /** + * put + * + * @param key1 + * @param key2 + * @param value + */ + public void put(K1 key1, K2 key2, V value) { + if (key1 == null || key2 == null || value == null) { + return; + } + if (k1ConcurrentHashMapConcurrentHashMap.containsKey(key1)) { + ConcurrentHashMap concurrentHashMap = k1ConcurrentHashMapConcurrentHashMap.get(key1); + if (concurrentHashMap != null) { + concurrentHashMap.put(key2, value); + } else { + concurrentHashMap = new ConcurrentHashMap(); + concurrentHashMap.put(key2, value); + k1ConcurrentHashMapConcurrentHashMap.put(key1, concurrentHashMap); + } + } else { + ConcurrentHashMap hashMap = new ConcurrentHashMap(); + hashMap.put(key2, value); + k1ConcurrentHashMapConcurrentHashMap.put(key1, hashMap); + } + } + + public Set getFirstKeys() { + return k1ConcurrentHashMapConcurrentHashMap.keySet(); + } + + /** + * get + * + * @param key1 + * @return ConcurrentHashMap + */ + public ConcurrentHashMap get(K1 key1) { + return k1ConcurrentHashMapConcurrentHashMap.get(key1); + } + + /** + * get + * + * @param key1 + * @param key2 + * @return V + */ + public V get(K1 key1, K2 key2) { + ConcurrentHashMap map = k1ConcurrentHashMapConcurrentHashMap.get(key1); + return map == null ? null : map.get(key2); + } + + /** + * getAllValues + * + * @param key1 + * @return Collection + */ + public Collection getAllValues(K1 key1) { + ConcurrentHashMap hashMap = k1ConcurrentHashMapConcurrentHashMap.get(key1); + return hashMap == null ? null : hashMap.values(); + } + + /** + * getAllValues + * + * @return Collection + */ + public Collection getAllValues() { + Collection result = null; + Set k1Set = k1ConcurrentHashMapConcurrentHashMap.keySet(); + if (k1Set != null) { + result = new ArrayList(); + for (K1 k1 : k1Set) { + ConcurrentHashMap value1 = k1ConcurrentHashMapConcurrentHashMap.get(k1); + if (value1 != null) { + Collection values = value1.values(); + if (values != null) { + result.addAll(values); + } + } + } + } + return result; + } + + /** + * containsKey + * + * @param key1 + * @param key2 + * @return boolean + */ + public boolean containsKey(K1 key1, K2 key2) { + if (k1ConcurrentHashMapConcurrentHashMap.containsKey(key1)) { + ConcurrentHashMap value1 = k1ConcurrentHashMapConcurrentHashMap.get(key1); + if (value1 != null) { + return value1.containsKey(key2); + } + } + return false; + } + + /** + * containsKey + * + * @param key1 + * @return boolean + */ + public boolean containsKey(K1 key1) { + return k1ConcurrentHashMapConcurrentHashMap.containsKey(key1); + } + + /** + * size + * + * @return int + */ + public int size() { + if (k1ConcurrentHashMapConcurrentHashMap.size() == 0) { + return 0; + } + int result = 0; + for (ConcurrentHashMap hashMap : k1ConcurrentHashMapConcurrentHashMap.values()) { + result += hashMap.size(); + } + return result; + } + + /** + * remove + * + * @param key1 + */ + public void remove(K1 key1) { + k1ConcurrentHashMapConcurrentHashMap.remove(key1); + } + + /** + * remove + * + * @param key1 + * @param key2 + */ + public void remove(K1 key1, K2 key2) { + ConcurrentHashMap hashMap = k1ConcurrentHashMapConcurrentHashMap.get(key1); + if (hashMap != null) { + hashMap.remove(key2); + } + if (hashMap == null || hashMap.isEmpty()) { + k1ConcurrentHashMapConcurrentHashMap.remove(key1); + } + } + + /** + * clear + */ + public void clear() { + if (k1ConcurrentHashMapConcurrentHashMap.size() > 0) { + for (ConcurrentHashMap hashMap : k1ConcurrentHashMapConcurrentHashMap.values()) { + hashMap.clear(); + } + k1ConcurrentHashMapConcurrentHashMap.clear(); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/FileUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/FileUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..fe8d194fcff26f3541f79377a1f0151efe5f726d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/FileUtil.java @@ -0,0 +1,171 @@ +package com.wordplat.quickstart.xutils.common.util; + +import com.wordplat.quickstart.xutils.x; + +import ohos.app.Environment; +import ohos.data.usage.DataUsage; +import ohos.data.usage.StatVfs; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * FileUtil + * + * @since 2021-05-08 + */ +public class FileUtil { + private FileUtil() { + } + + /** + * getCacheDir + * + * @param dirName + * @return File + */ + public static File getCacheDir(String dirName) { + File result = null; + if (isDiskAvailable()) { + File cacheDir = x.app().getExternalCacheDir(); + if (cacheDir != null) { + result = new File(cacheDir, dirName); + } + } + if (result == null) { + result = new File(x.app().getCacheDir(), dirName); + } + if (result.exists() || result.mkdirs()) { + return result; + } else { + return null; + } + } + + /** + * 检查磁盘空间是否大于10mb + * + * @return true 大于 + */ + public static boolean isDiskAvailable() { + long size = getDiskAvailableSize(); + return size > 10 * 1024 * 1024L; // > 10bm + } + + /** + * getDiskAvailableSize + * + * @return long + */ + public static long getDiskAvailableSize() { + if (!existsSdcard()) { + return 0; + } + File path = new File(Environment.DIRECTORY_DOWNLOADS); // 取得sdcard文件路径 + StatVfs stat = null; + try { + stat = new StatVfs(path.getCanonicalPath()); + } catch (IOException e) { + LogUtil.e("error:" + e); + } + long blockSize = 0; + long availableBlocks = 0; + blockSize = stat.getSpace(); + availableBlocks = stat.getAvailableSpace(); + return availableBlocks * blockSize; + } + + /** + * existsSdcard + * + * @return Boolean + */ + public static Boolean existsSdcard() { + return DataUsage.isDiskEmulated(); + } + + /** + * getFileOrDirSize + * + * @param file + * @return long + */ + public static long getFileOrDirSize(File file) { + if (!file.exists()) { + return 0; + } + if (!file.isDirectory()) { + return file.length(); + } + + long length = 0; + File[] list = file.listFiles(); + if (list != null) { // 文件夹被删除时, 子文件正在被写入, 文件属性异常返回null. + for (File item : list) { + length += getFileOrDirSize(item); + } + } + + return length; + } + + /** + * 复制文件到指定文件 + * + * @param fromPath 源文件 + * @param toPath 复制到的文件 + * @return true 成功,false 失败 + */ + public static boolean copy(String fromPath, String toPath) { + boolean result = false; + File from = new File(fromPath); + if (!from.exists()) { + return result; + } + + File toFile = new File(toPath); + IOUtil.deleteFileOrDir(toFile); + File toDir = toFile.getParentFile(); + if (toDir.exists() || toDir.mkdirs()) { + FileInputStream in = null; + FileOutputStream out = null; + try { + in = new FileInputStream(from); + out = new FileOutputStream(toFile); + IOUtil.copy(in, out); + result = true; + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + result = false; + } finally { + IOUtil.closeQuietly(in); + IOUtil.closeQuietly(out); + } + } + return result; + } + + /** + * deleteFileOrDir + * + * @param path + * @return boolean + */ + public static boolean deleteFileOrDir(File path) { + if (path == null || !path.exists()) { + return true; + } + if (path.isFile()) { + return path.delete(); + } + File[] files = path.listFiles(); + if (files != null) { + for (File file : files) { + deleteFileOrDir(file); + } + } + return path.delete(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/IOUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/IOUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b478d0d6097820911864fdcb460aff72a49fe8ea --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/IOUtil.java @@ -0,0 +1,202 @@ +package com.wordplat.quickstart.xutils.common.util; + +import ohos.data.resultset.ResultSet; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +/** + * IOUtil + * + * @since 2021-05-08 + */ +public class IOUtil { + private IOUtil() { + } + + /** + * closeQuietly + * + * @param closeable + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + } + } + } + + /** + * closeQuietly + * + * @param cursor + */ + public static void closeQuietly(ResultSet cursor) { + if (cursor != null) { + try { + cursor.close(); + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + } + } + } + + /** + * readBytes + * + * @param in + * @return byte + * @throws IOException + */ + public static byte[] readBytes(InputStream in) throws IOException { + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + ByteArrayOutputStream out = null; + try { + out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } finally { + closeQuietly(out); + } + } + + /** + * readBytes + * + * @param in + * @param skip + * @param size + * @return byte + * @throws IOException + */ + public static byte[] readBytes(InputStream in, long skip, int size) throws IOException { + byte[] result = null; + if (skip > 0) { + long skipped = 0; + while (skip > 0 && (skipped = in.skip(skip)) > 0) { + skip -= skipped; + } + } + result = new byte[size]; + for (int i = 0; i < size; i++) { + result[i] = (byte) in.read(); + } + return result; + } + + /** + * readStr + * + * @param in + * @return String + * @throws IOException + */ + public static String readStr(InputStream in) throws IOException { + return readStr(in, "UTF-8"); + } + + /** + * readStr + * + * @param in + * @param charset + * @return String + * @throws IOException + */ + public static String readStr(InputStream in, String charset) throws IOException { + if (TextUtils.isEmpty(charset)) { + charset = "UTF-8"; + } + + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + Reader reader = new InputStreamReader(in, charset); + StringBuilder sb = new StringBuilder(); + char[] buf = new char[1024]; + int len; + while ((len = reader.read(buf)) >= 0) { + sb.append(buf, 0, len); + } + return sb.toString(); + } + + /** + * writeStr + * + * @param out + * @param str + * @throws IOException + */ + public static void writeStr(OutputStream out, String str) throws IOException { + writeStr(out, str, "UTF-8"); + } + + /** + * writeStr + * + * @param out + * @param str + * @param charset + * @throws IOException + */ + public static void writeStr(OutputStream out, String str, String charset) throws IOException { + if (TextUtils.isEmpty(charset)) { + charset = "UTF-8"; + } + Writer writer = new OutputStreamWriter(out, charset); + writer.write(str); + writer.flush(); + } + + /** + * copy + * + * @param in + * @param out + * @throws IOException + */ + public static void copy(InputStream in, OutputStream out) throws IOException { + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + if (!(out instanceof BufferedOutputStream)) { + out = new BufferedOutputStream(out); + } + int len = 0; + byte[] buffer = new byte[1024]; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); + } + + /** + * deleteFileOrDir + * + * @param path + * @return boolean + */ + public static boolean deleteFileOrDir(File path) { + return FileUtil.deleteFileOrDir(path); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/KeyValue.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/KeyValue.java new file mode 100644 index 0000000000000000000000000000000000000000..f044ac833519f1ca3a76bb6d3edf31e22b38b5c2 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/KeyValue.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.common.util; + +/** + * KeyValue + * + * @since 2021-05-08 + */ +public class KeyValue { + /** + * key + */ + public final String key; + /** + * value + */ + public final Object value; + + /** + * KeyValue + * + * @param key + * @param value + */ + public KeyValue(String key, Object value) { + this.key = key; + this.value = value; + } + + public String getValueStrOrEmpty() { + return value == null ? "" : value.toString(); + } + + public String getValueStrOrNull() { + return value == null ? null : value.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + KeyValue keyValue = (KeyValue) obj; + + return key == null ? keyValue.key == null : key.equals(keyValue.key); + } + + @Override + public int hashCode() { + return key != null ? key.hashCode() : 0; + } + + @Override + public String toString() { + return "KeyValue{" + "key='" + key + '\'' + ", value=" + value + '}'; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/LogUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/LogUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..09af657a24eddfa33f707d15a2c2a3328e18d533 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/LogUtil.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.common.util; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import com.wordplat.quickstart.xutils.x; + +import java.util.Locale; + +/** + * tag自动产生,格式: customTagPrefix:className.methodName(L:lineNumber), + * customTagPrefix为空时只输出:className.methodName(L:lineNumber)。 + * Author: wyouflf + * Date: 13-7-24 + * Time: 下午12:23 + * + * @since 2021-05-08 + */ +public class LogUtil { + private static String customTagPrefix = "x_log"; + private static final String TAG_LOG = "LogUtil"; + + private static final int DOMAIN_ID = 0xD000F00; + + private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, DOMAIN_ID, LogUtil.TAG_LOG); + private static final String LOG_FORMAT = "%{public}s: %{public}s"; + + private LogUtil() { + } + + private static String generateTag() { + StackTraceElement caller = new Throwable().getStackTrace()[2]; + String tag = "%s.%s(L:%d)"; + String callerClazzName = caller.getClassName(); + callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1); + tag = String.format(Locale.getDefault(), tag, callerClazzName, caller.getMethodName(), caller.getLineNumber()); + tag = TextUtils.isEmpty(customTagPrefix) ? tag : customTagPrefix + ":" + tag; + return tag; + } + + /** + * d + * + * @param content + */ + public static void d(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * d + * + * @param content + * @param tr + */ + public static void d(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * e + * + * @param content + */ + public static void e(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.error(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * e + * + * @param content + * @param tr + */ + public static void e(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.error(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * i + * + * @param content + */ + public static void i(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * i + * + * @param content + * @param tr + */ + public static void i(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * v + * + * @param content + */ + public static void v(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * v + * + * @param content + * @param tr + */ + public static void v(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * w + * + * @param content + */ + public static void w(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * w + * + * @param content + * @param tr + */ + public static void w(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * w + * + * @param tr + */ + public static void w(Throwable tr) { + if (!x.isDebug()) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, tr); + } + + /** + * wtf + * + * @param content + */ + public static void wtf(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * wtf + * + * @param content + * @param tr + */ + public static void wtf(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, content); + } + + /** + * wtf + * + * @param tr + */ + public static void wtf(Throwable tr) { + if (!x.isDebug()) { + return; + } + String tag = generateTag(); + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, tr); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/MD5.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/MD5.java new file mode 100644 index 0000000000000000000000000000000000000000..9735ca4949a646be7335a7cd9debf853edd43bc2 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/MD5.java @@ -0,0 +1,92 @@ +package com.wordplat.quickstart.xutils.common.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * \ + * MD5 + * + * @since 2021-05-08 + */ +public final class MD5 { + private static final char[] CHARS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private MD5() { + } + + /** + * toHexString + * + * @param bytes + * @return String + */ + public static String toHexString(byte[] bytes) { + if (bytes == null) { + return ""; + } + StringBuilder hex = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + hex.append(CHARS[(b >> 4) & 0x0F]); + hex.append(CHARS[b & 0x0F]); + } + return hex.toString(); + } + + /** + * md5 + * + * @param file + * @return String + * @throws IOException + * @throws RuntimeException + */ + public static String md5(File file) throws IOException { + MessageDigest messagedigest = null; + FileInputStream in = null; + FileChannel ch = null; + byte[] encodeBytes = null; + try { + messagedigest = MessageDigest.getInstance("MD5"); + in = new FileInputStream(file); + ch = in.getChannel(); + MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + messagedigest.update(byteBuffer); + encodeBytes = messagedigest.digest(); + } catch (NoSuchAlgorithmException neverHappened) { + throw new RuntimeException(neverHappened); + } finally { + IOUtil.closeQuietly(in); + IOUtil.closeQuietly(ch); + } + + return toHexString(encodeBytes); + } + + /** + * md5 + * + * @param string + * @return String + * @throws RuntimeException + */ + public static String md5(String string) { + byte[] encodeBytes = null; + try { + encodeBytes = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException neverHappened) { + throw new RuntimeException(neverHappened); + } catch (UnsupportedEncodingException neverHappened) { + throw new RuntimeException(neverHappened); + } + + return toHexString(encodeBytes); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ParameterizedTypeUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ParameterizedTypeUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..6e27d28001a5b8f824892ba3181bd24286e36aad --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ParameterizedTypeUtil.java @@ -0,0 +1,102 @@ +package com.wordplat.quickstart.xutils.common.util; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +/** + * ParameterizedTypeUtil + * + * @since 2021-05-08 + */ +public class ParameterizedTypeUtil { + private ParameterizedTypeUtil() { + } + + /** + * getParameterizedType + * + * @param ownerType + * @param declaredClass + * @param paramIndex + * @return Type + * @throws IllegalArgumentException + */ + public static Type getParameterizedType( + final Type ownerType, + final Class declaredClass, + int paramIndex) { + Class clazz = null; + ParameterizedType pt = null; + Type[] ats = null; + TypeVariable[] tps = null; + if (ownerType instanceof ParameterizedType) { + pt = (ParameterizedType) ownerType; + clazz = (Class) pt.getRawType(); + ats = pt.getActualTypeArguments(); + tps = clazz.getTypeParameters(); + } else { + clazz = (Class) ownerType; + } + if (declaredClass == clazz) { + if (ats != null) { + return ats[paramIndex]; + } + return Object.class; + } + + Type[] types = clazz.getGenericInterfaces(); + if (types != null) { + for (int i = 0; i < types.length; i++) { + Type t = types[i]; + if (t instanceof ParameterizedType) { + Class cls = (Class) ((ParameterizedType) t).getRawType(); + if (declaredClass.isAssignableFrom(cls)) { + try { + return getTrueType(getParameterizedType(t, declaredClass, paramIndex), tps, ats); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + } + } + } + } + + Class superClass = clazz.getSuperclass(); + if (superClass != null) { + if (declaredClass.isAssignableFrom(superClass)) { + return getTrueType( + getParameterizedType(clazz.getGenericSuperclass(), + declaredClass, paramIndex), tps, ats); + } + } + throw new IllegalArgumentException("FindGenericType:" + ownerType + + ", declaredClass: " + declaredClass + ", index: " + paramIndex); + } + + private static Type getTrueType( + Type type, + TypeVariable[] typeVariables, + Type[] actualTypes) { + if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type; + String name = tv.getName(); + if (actualTypes != null) { + for (int i = 0; i < typeVariables.length; i++) { + if (name.equals(typeVariables[i].getName())) { + return actualTypes[i]; + } + } + } + return tv; + } else if (type instanceof GenericArrayType) { + Type ct = ((GenericArrayType) type).getGenericComponentType(); + if (ct instanceof Class) { + return Array.newInstance((Class) ct, 0).getClass(); + } + } + return type; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ProcessLock.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ProcessLock.java new file mode 100644 index 0000000000000000000000000000000000000000..53d7ac829244efa485b0d5fabe346e5094de999a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/ProcessLock.java @@ -0,0 +1,257 @@ +package com.wordplat.quickstart.xutils.common.util; + +import ohos.app.Context; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.text.DecimalFormat; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.wordplat.quickstart.xutils.x; + +/** + * 进程间锁, 仅在同一个应用中有效. + * + * @since 2021-05-08 + */ +public final class ProcessLock implements Closeable { + + private static final String LOCK_FILE_DIR = "process_lock"; + /** + * key1: lockName + * key2: fileLock.hashCode() + */ + private static final DoubleKeyValueMap LOCK_MAP = new DoubleKeyValueMap(); + private static final DecimalFormat FORMAT = new DecimalFormat("0.##################"); + private final String mLockName; + private final FileLock mFileLock; + private final File mFile; + private final Closeable mStream; + private final boolean mWriteMode; + + static { + File dir = x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE); + IOUtil.deleteFileOrDir(dir); + } + + private ProcessLock(String lockName, File file, FileLock fileLock, Closeable stream, boolean writeMode) { + mLockName = lockName; + mFileLock = fileLock; + mFile = file; + mStream = stream; + mWriteMode = writeMode; + } + + /** + * isValid + * + * @param fileLock + * @return + */ + private static boolean isValid(FileLock fileLock) { + return fileLock != null && fileLock.isValid(); + } + + /** + * release + * + * @param lockName + * @param fileLock + * @param file + * @param stream + */ + private static void release(String lockName, FileLock fileLock, File file, Closeable stream) { + synchronized (LOCK_MAP) { + if (fileLock != null) { + try { + LOCK_MAP.remove(lockName, fileLock.hashCode()); + ConcurrentHashMap locks = LOCK_MAP.get(lockName); + if (locks == null || locks.isEmpty()) { + IOUtil.deleteFileOrDir(file); + } + + if (fileLock.channel().isOpen()) { + fileLock.release(); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(fileLock.channel()); + } + } + + IOUtil.closeQuietly(stream); + + LOCK_MAP.notifyAll(); + } + } + + /** + * tryLock + * + * @param lockName + * @param writeMode + * @return ProcessLock + */ + public static ProcessLock tryLock(final String lockName, final boolean writeMode) { + return tryLockInternal(lockName, customHash(lockName), writeMode); + } + + /** + * tryLock + * + * @param lockName + * @param writeMode + * @param maxWaitTimeMillis + * @return ProcessLock + * @throws InterruptedException + */ + public static ProcessLock tryLock(final String lockName, final boolean writeMode, final long maxWaitTimeMillis) throws InterruptedException { + ProcessLock lock = null; + long expiryTime = System.currentTimeMillis() + maxWaitTimeMillis; + String hash = customHash(lockName); + synchronized (LOCK_MAP) { + while (System.currentTimeMillis() < expiryTime) { + lock = tryLockInternal(lockName, hash, writeMode); + if (lock != null) { + break; + } else { + try { + LOCK_MAP.wait(10); + } catch (InterruptedException iex) { + throw iex; + } catch (Throwable ignored) { + LogUtil.e("error:" + ignored); + } + } + } + } + + return lock; + } + + /** + * isValid + * + * @return boolean + */ + public boolean isValid() { + return isValid(mFileLock); + } + + /** + * release + */ + public void release() { + release(mLockName, mFileLock, mFile, mStream); + } + + @Override + public void close() throws IOException { + release(); + } + + /** + * 取得字符串的自定义hash值, 尽量保证255字节内的hash不重复. + * + * @param str + * @return + */ + private static String customHash(String str) { + if (TextUtils.isEmpty(str)) { + return "0"; + } + double hash = 0.0; + byte[] bytes = str.getBytes(); + for (int i = 0; i < str.length(); i++) { + hash = (255.0 * hash + bytes[i]) * 0.005; + } + return FORMAT.format(hash); + } + + private static ProcessLock tryLockInternal(final String lockName, final String hash, final boolean writeMode) { + synchronized (LOCK_MAP) { + ConcurrentHashMap locks = LOCK_MAP.get(lockName); + if (locks != null && !locks.isEmpty()) { + Iterator> itr = locks.entrySet().iterator(); + while (itr.hasNext()) { + Map.Entry entry = itr.next(); + ProcessLock value = entry.getValue(); + if (value != null) { + if (!value.isValid()) { + itr.remove(); + } else if (writeMode) { + return null; + } else if (value.mWriteMode) { + return null; + } + } else { + itr.remove(); + } + } + } + + FileChannel channel = null; + Closeable stream = null; + try { + File file = new File( + x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE), + hash); + if (file.exists() || file.createNewFile()) { + if (writeMode) { + FileOutputStream out = new FileOutputStream(file, false); + channel = out.getChannel(); + stream = out; + } else { + FileInputStream in = new FileInputStream(file); + channel = in.getChannel(); + stream = in; + } + if (channel != null) { + FileLock fileLock = channel.tryLock(0L, Long.MAX_VALUE, !writeMode); + if (isValid(fileLock)) { + ProcessLock result = new ProcessLock(lockName, file, fileLock, stream, writeMode); + LOCK_MAP.put(lockName, fileLock.hashCode(), result); + return result; + } else { + release(lockName, fileLock, file, stream); + } + } else { + throw new IOException("can not get file channel:" + file.getCanonicalPath()); + } + } + } catch (Throwable ex) { + LogUtil.d("tryLock: " + lockName + ", " + ex.getMessage()); + IOUtil.closeQuietly(stream); + IOUtil.closeQuietly(channel); + } + + LOCK_MAP.notifyAll(); + } + + return null; + } + + @Override + public String toString() { + return mLockName + ": " + mFile.getName(); + } + + /** + * finalize + * + * @throws Throwable + */ + @Override + protected void finalize() throws Throwable { + super.finalize(); + this.release(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/TextUtils.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/TextUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..cee53789ae6d218f79790f3b14613ccf5e5d0ef3 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/TextUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.common.util; + +import java.util.Iterator; + +/** + * TextUtils + * + * @since 2021-05-08 + */ +public class TextUtils { + private TextUtils() { + } + + /** + * isEmpty + * + * @param str + * @return boolean + */ + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + /** + * join + * + * @param delimiter + * @param tokens + * @return String + */ + public static String join(CharSequence delimiter, Object[] tokens) { + final int length = tokens.length; + if (length == 0) { + return ""; + } + final StringBuilder sb = new StringBuilder(); + sb.append(tokens[0]); + for (int number = 1; number < length; number++) { + sb.append(delimiter); + sb.append(tokens[number]); + } + return sb.toString(); + } + + /** + * join + * + * @param delimiter + * @param tokens + * @return String + */ + public static String join(CharSequence delimiter, Iterable tokens) { + final Iterator it = tokens.iterator(); + if (!it.hasNext()) { + return ""; + } + final StringBuilder sb = new StringBuilder(); + sb.append(it.next()); + while (it.hasNext()) { + sb.append(delimiter); + sb.append(it.next()); + } + return sb.toString(); + } + + /** + * isDigitsOnly + * + * @param str + * @return boolean + */ + public static boolean isDigitsOnly(CharSequence str) { + final int len = str.length(); + for (int cp, number = 0; number < len; number += Character.charCount(cp)) { + cp = Character.codePointAt(str, number); + if (!Character.isDigit(cp)) { + return false; + } + } + return true; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/URLUtil.java b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/URLUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..3789f7155cd24921959f51cd7ff944faafdfd01f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/common/util/URLUtil.java @@ -0,0 +1,318 @@ +package com.wordplat.quickstart.xutils.common.util; + +import java.io.UnsupportedEncodingException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * URLUtil + * + * @since 2021-05-08 + */ +public final class URLUtil { + private static final Pattern CONTENT_DISPOSITION_PATTERN = + Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$", Pattern.CASE_INSENSITIVE); + + private static final String LOGTAG = "webkit"; + private static final boolean TRACE = false; + /** + * to refer to bar.png under your package's asset/foo/ directory, use + */ + private static final String ASSET_BASE = "file:///ohos_asset/"; + /** + * // to refer to bar.png under your package's res/drawable/ directory, use + * // "file:///ohos_asset/drawable/bar.png". Use "drawable" to refer to + * // "drawable-hdpi" directory as well. + */ + private static final String RESOURCE_BASE = "file:///ohos_asset/"; + private static final String FILE_BASE = "file:"; + private static final String PROXY_BASE = "file:///cookieless_proxy/"; + private static final String CONTENT_BASE = "content:"; + + private URLUtil() { + } + + /** + * composeSearchUrl + * + * @param inQuery + * @param template + * @param queryPlaceHolder + * @return String + */ + public static String composeSearchUrl(String inQuery, String template, + String queryPlaceHolder) { + int placeHolderIndex = template.indexOf(queryPlaceHolder); + if (placeHolderIndex < 0) { + return null; + } + + String query; + StringBuilder buffer = new StringBuilder(); + buffer.append(template.substring(0, placeHolderIndex)); + + try { + query = java.net.URLEncoder.encode(inQuery, "utf-8"); + buffer.append(query); + } catch (UnsupportedEncodingException ex) { + return null; + } + + buffer.append(template.substring( + placeHolderIndex + queryPlaceHolder.length())); + + return buffer.toString(); + } + + /** + * decode + * + * @param url + * @return byte + * @throws IllegalArgumentException + */ + public static byte[] decode(byte[] url) throws IllegalArgumentException { + if (url.length == 0) { + return new byte[0]; + } + + // Create a new byte array with the same length to ensure capacity + byte[] tempData = new byte[url.length]; + + int tempCount = 0; + for (int i = 0; i < url.length; i++) { + byte b = url[i]; + if (b == '%') { + if (url.length - i > 2) { + b = (byte) (parseHex(url[i + 1]) * 16 + + parseHex(url[i + 2])); + i += 2; + } else { + throw new IllegalArgumentException("Invalid format"); + } + } + tempData[tempCount++] = b; + } + byte[] retData = new byte[tempCount]; + System.arraycopy(tempData, 0, retData, 0, tempCount); + return retData; + } + + /** + * verifyURLEncoding + * + * @param url + * @return boolean + */ + static boolean verifyURLEncoding(String url) { + int count = url.length(); + if (count == 0) { + return false; + } + + int index = url.indexOf('%'); + while (index >= 0 && index < count) { + if (index < count - 2) { + try { + parseHex((byte) url.charAt(++index)); + parseHex((byte) url.charAt(++index)); + } catch (IllegalArgumentException e) { + return false; + } + } else { + return false; + } + index = url.indexOf('%', index + 1); + } + return true; + } + + private static int parseHex(byte b) { + if (b >= '0' && b <= '9') { + return (b - '0'); + } + if (b >= 'A' && b <= 'F') { + return (b - 'A' + 10); + } + if (b >= 'a' && b <= 'f') { + return (b - 'a' + 10); + } + + throw new IllegalArgumentException("Invalid hex char '" + b + "'"); + } + + /** + * isAssetUrl + * + * @param url + * @return boolean + */ + public static boolean isAssetUrl(String url) { + return (null != url) && url.startsWith(ASSET_BASE); + } + + /** + * isResourceUrl + * + * @param url + * @return boolean + */ + public static boolean isResourceUrl(String url) { + return (null != url) && url.startsWith(RESOURCE_BASE); + } + + /** + * isCookielessProxyUrl + * + * @param url + * @return boolean + */ + @Deprecated + public static boolean isCookielessProxyUrl(String url) { + return (null != url) && url.startsWith(PROXY_BASE); + } + + /** + * isFileUrl + * + * @param url + * @return boolean + */ + public static boolean isFileUrl(String url) { + return (null != url) && (url.startsWith(FILE_BASE) + && !url.startsWith(ASSET_BASE) + && !url.startsWith(PROXY_BASE)); + } + + /** + * isAboutUrl + * + * @param url + * @return boolean + */ + public static boolean isAboutUrl(String url) { + return (null != url) && url.startsWith("about:"); + } + + /** + * isDataUrl + * + * @param url + * @return boolean + */ + public static boolean isDataUrl(String url) { + return (null != url) && url.startsWith("data:"); + } + + /** + * isJavaScriptUrl + * + * @param url + * @return boolean + */ + public static boolean isJavaScriptUrl(String url) { + return (null != url) && url.startsWith("javascript:"); + } + + /** + * isHttpUrl + * + * @param url + * @return boolean + */ + public static boolean isHttpUrl(String url) { + return (null != url) + && (url.length() > 6) + && url.substring(0, 7).equalsIgnoreCase("http://"); + } + + /** + * isHttpsUrl + * + * @param url + * @return boolean + */ + public static boolean isHttpsUrl(String url) { + return (null != url) + && (url.length() > 7) + && url.substring(0, 8).equalsIgnoreCase("https://"); + } + + /** + * isNetworkUrl + * + * @param url + * @return boolean + */ + public static boolean isNetworkUrl(String url) { + if (url == null || url.length() == 0) { + return false; + } + return isHttpUrl(url) || isHttpsUrl(url); + } + + /** + * isContentUrl + * + * @param url + * @return boolean + */ + public static boolean isContentUrl(String url) { + return (null != url) && url.startsWith(CONTENT_BASE); + } + + /** + * isValidUrl + * + * @param url + * @return boolean + */ + public static boolean isValidUrl(String url) { + if (url == null || url.length() == 0) { + return false; + } + + return (isAssetUrl(url) + || isResourceUrl(url) + || isFileUrl(url) + || isAboutUrl(url) + || isHttpUrl(url) + || isHttpsUrl(url) + || isJavaScriptUrl(url) + || isContentUrl(url)); + } + + /** + * stripAnchor + * + * @param url + * @return String + */ + public static String stripAnchor(String url) { + int anchorIndex = url.indexOf('#'); + if (anchorIndex != -1) { + return url.substring(0, anchorIndex); + } + return url; + } + + + /** + * parseContentDisposition + * + * @param contentDisposition + * @return String + */ + static String parseContentDisposition(String contentDisposition) { + try { + Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); + if (matcher.find()) { + return matcher.group(2); + } + } catch (IllegalStateException ex) { + // This function is defined as returning null when it can't parse the header + LogUtil.e("error:" + ex); + } + return null; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/config/DbConfigs.java b/entry/src/main/java/com/wordplat/quickstart/xutils/config/DbConfigs.java new file mode 100644 index 0000000000000000000000000000000000000000..62650d65157474cafc8a90d2509a6ca889364589 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/config/DbConfigs.java @@ -0,0 +1,66 @@ +package com.wordplat.quickstart.xutils.config; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.ex.DbException; + +/** + * Created by wyouflf on 15/7/31. + * 全局db配置 + * + * @since 2021-05-09 + */ +public enum DbConfigs { + /** + * HTTP + */ + HTTP(new DbManager.DaoConfig() + .setDbName("xUtils_http_cache.db") + .setDbVersion(2) + .setDbOpenListener(new DbManager.DbOpenListener() { + @Override + public void onDbOpened(DbManager db) { + } + }) + .setDbUpgradeListener(new DbManager.DbUpgradeListener() { + @Override + public void onUpgrade(DbManager db, int oldVersion, int newVersion) { + try { + db.dropDb(); + } catch (DbException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + })), + /** + * COOKIE + */ + COOKIE(new DbManager.DaoConfig() + .setDbName("xUtils_http_cookie.db") + .setDbVersion(1) + .setDbOpenListener(new DbManager.DbOpenListener() { + @Override + public void onDbOpened(DbManager db) { + } + }) + .setDbUpgradeListener(new DbManager.DbUpgradeListener() { + @Override + public void onUpgrade(DbManager db, int oldVersion, int newVersion) { + try { + db.dropDb(); // 默认删除所有表 + } catch (DbException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + })); + + private DbManager.DaoConfig config; + + DbConfigs(DbManager.DaoConfig config) { + this.config = config; + } + + public DbManager.DaoConfig getConfig() { + return config; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/CursorUtils.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/CursorUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..bdc70ecaaaafffbbab53aafbf1154ea8c72101b4 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/CursorUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db; + +import com.wordplat.quickstart.xutils.db.table.ColumnEntity; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.db.table.TableEntity; + +import ohos.data.resultset.ResultSet; + +import java.util.HashMap; + +final class CursorUtils { + private CursorUtils() { + } + + /** + * getEntity + * + * @param table + * @param cursor + * @param + * @return T + * @throws Throwable + */ + public static T getEntity(TableEntity table, final ResultSet cursor) throws Throwable { + T entity = table.createEntity(); + HashMap columnMap = table.getColumnMap(); + int columnCount = cursor.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + String columnName = cursor.getColumnNameForIndex(i); + ColumnEntity column = columnMap.get(columnName); + if (column != null) { + column.setValueFromCursor(entity, cursor, i); + } + } + return entity; + } + + /** + * getDbModel + * + * @param cursor + * @return DbModel + */ + public static DbModel getDbModel(final ResultSet cursor) { + DbModel result = new DbModel(); + int columnCount = cursor.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + result.add(cursor.getColumnNameForIndex(i), cursor.getString(i)); + } + return result; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbManagerImpl.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4b0db4bdb7df4433dd06d32e517136dce0d208a1 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbManagerImpl.java @@ -0,0 +1,674 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.db.sqlite.SqlInfo; +import com.wordplat.quickstart.xutils.db.sqlite.SqlInfoBuilder; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.db.table.ColumnEntity; +import com.wordplat.quickstart.xutils.db.table.DbBase; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.db.table.TableEntity; +import com.wordplat.quickstart.xutils.ex.DbException; +import com.wordplat.quickstart.xutils.x; + +import ohos.data.DatabaseHelper; +import ohos.data.rdb.RdbOpenCallback; +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.Statement; +import ohos.data.rdb.StoreConfig; +import ohos.data.resultset.ResultSet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * checkstyle:RegexpSingleline + */ +public final class DbManagerImpl extends DbBase { + /** + * key: dbName + */ + private static final HashMap DAO_MAP = new HashMap(); + + private RdbStore database; + private DaoConfig daoConfig; + private boolean allowTransaction; + + /** + * DbManagerImpl + * + * @param config + * @throws DbException + */ + private DbManagerImpl(DaoConfig config) throws DbException { + if (config == null) { + throw new IllegalArgumentException("daoConfig may not be null"); + } + + this.daoConfig = config; + this.allowTransaction = config.isAllowTransaction(); + try { + this.database = openOrCreateDatabase(config); + DbOpenListener dbOpenListener = config.getDbOpenListener(); + if (dbOpenListener != null) { + dbOpenListener.onDbOpened(this); + } + } catch (DbException ex) { + /** + * IOUtil.closeQuietly(this.database); + */ + throw ex; + } catch (Throwable ex) { + /** + * IOUtil.closeQuietly(this.database); + */ + throw new DbException(ex.getMessage(), ex); + } + } + + /** + * getInstance + * + * @param daoConfig + * @return DbManager + * @throws DbException + */ + public static synchronized DbManager getInstance(DaoConfig daoConfig) throws DbException { + if (daoConfig == null) { + /** + * 使用默认配置 + */ + daoConfig = new DaoConfig(); + } + + DbManagerImpl dao = DAO_MAP.get(daoConfig); + if (dao == null) { + dao = new DbManagerImpl(daoConfig); + DAO_MAP.put(daoConfig, dao); + } else { + dao.daoConfig = daoConfig; + } + + // update the database if needed + RdbStore database = dao.database; + int oldVersion = database.getVersion(); + int newVersion = daoConfig.getDbVersion(); + if (oldVersion != newVersion) { + if (oldVersion != 0) { + DbUpgradeListener upgradeListener = daoConfig.getDbUpgradeListener(); + if (upgradeListener != null) { + upgradeListener.onUpgrade(dao, oldVersion, newVersion); + } else { + dao.dropDb(); + } + } + database.setVersion(newVersion); + } + + return dao; + } + + @Override + public RdbStore getDatabase() { + return database; + } + + @Override + public DaoConfig getDaoConfig() { + return daoConfig; + } + + /** + * saveOrUpdate + * + * @param entity + * @throws DbException + */ + @Override + public void saveOrUpdate(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + saveOrUpdateWithoutTransaction(table, item); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + saveOrUpdateWithoutTransaction(table, entity); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + /** + * replace + * + * @param entity + * @throws DbException + */ + @Override + public void replace(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + /** + * save + * + * @param entity + * @throws DbException + */ + @Override + public void save(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + /** + * saveBindingId + * + * @param entity + * @return boolean + * @throws DbException + */ + @Override + public boolean saveBindingId(Object entity) throws DbException { + boolean result = false; + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return false; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + if (!saveBindingIdWithoutTransaction(table, item)) { + throw new DbException("saveBindingId error, transaction will not commit!"); + } + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + result = saveBindingIdWithoutTransaction(table, entity); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + return result; + } + + /** + * deleteById + * + * @param entityType + * @param idValue + * @throws DbException + */ + @Override + public void deleteById(Class entityType, Object idValue) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) { + return; + } + try { + beginTransaction(); + + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfoById(table, idValue)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void delete(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + if (!table.tableIsExists()) { + return; + } + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + if (!table.tableIsExists()) { + return; + } + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void delete(Class entityType) throws DbException { + delete(entityType, null); + } + + @Override + public int delete(Class entityType, WhereBuilder whereBuilder) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) { + return 0; + } + int result = 0; + try { + beginTransaction(); + + result = executeUpdateDelete(SqlInfoBuilder.buildDeleteSqlInfo(table, whereBuilder)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + return result; + } + + @Override + public void update(Object entity, String... updateColumnNames) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) { + return; + } + TableEntity table = this.getTable(entities.get(0).getClass()); + if (!table.tableIsExists()) { + return; + } + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, item, updateColumnNames)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + if (!table.tableIsExists()) { + return; + } + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity, updateColumnNames)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public int update(Class entityType, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) { + return 0; + } + + int result = 0; + try { + beginTransaction(); + + result = executeUpdateDelete(SqlInfoBuilder.buildUpdateSqlInfo(table, whereBuilder, nameValuePairs)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + + return result; + } + + @Override + public T findById(Class entityType, Object idValue) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) { + return null; + } + + Selector selector = Selector.from(table).where(table.getId().getName(), "=", idValue); + String sql = selector.limit(1).toString(); + ResultSet cursor = execQuery(sql); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + return CursorUtils.getEntity(table, cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + @Override + public T findFirst(Class entityType) throws DbException { + return this.selector(entityType).findFirst(); + } + + @Override + public List findAll(Class entityType) throws DbException { + return this.selector(entityType).findAll(); + } + + @Override + public Selector selector(Class entityType) throws DbException { + return Selector.from(this.getTable(entityType)); + } + + @Override + public DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException { + ResultSet cursor = execQuery(sqlInfo); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + return CursorUtils.getDbModel(cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + @Override + public List findDbModelAll(SqlInfo sqlInfo) throws DbException { + List dbModelList = new ArrayList(); + + ResultSet cursor = execQuery(sqlInfo); + if (cursor != null) { + try { + while (cursor.goToNextRow()) { + dbModelList.add(CursorUtils.getDbModel(cursor)); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return dbModelList; + } + + private RdbStore openOrCreateDatabase(DaoConfig config) { + RdbStore result = null; + /** + File dbDir = config.getDbDir(); + if (dbDir != null && (dbDir.exists() || dbDir.mkdirs())) { + File dbFile = new File(dbDir, config.getDbName()); + */ + StoreConfig storeConfig = StoreConfig.newDefaultConfig(config.getDbName()); + RdbOpenCallback callback = new RdbOpenCallback() { + @Override + public void onCreate(RdbStore store) { + } + + @Override + public void onUpgrade(RdbStore store, int oldVersion, int newVersion) { + } + }; + DatabaseHelper helper = new DatabaseHelper(x.app()); + result = helper.getRdbStore(storeConfig, 1, callback, null); + /** + * // } else { + * // result = x.app().op(config.getDbName(), 0, null); + * // } + */ + return result; + } + + private void saveOrUpdateWithoutTransaction(TableEntity table, Object entity) throws DbException { + ColumnEntity id = table.getId(); + if (id.isAutoId()) { + if (id.getColumnValue(entity) != null) { + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity)); + } else { + saveBindingIdWithoutTransaction(table, entity); + } + } else { + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity)); + } + } + + private boolean saveBindingIdWithoutTransaction(TableEntity table, Object entity) throws DbException { + ColumnEntity id = table.getId(); + if (id.isAutoId()) { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + long idValue = getLastAutoIncrementId(table.getName()); + if (idValue == -1) { + return false; + } + id.setAutoIdValue(entity, idValue); + return true; + } else { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + return true; + } + } + + private long getLastAutoIncrementId(String tableName) throws DbException { + long id = -1; + ResultSet cursor = execQuery("SELECT seq FROM sqlite_sequence WHERE name='" + tableName + "' LIMIT 1"); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + id = cursor.getLong(0); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return id; + } + + @Override + public void close() throws IOException { + if (DAO_MAP.containsKey(daoConfig)) { + DAO_MAP.remove(daoConfig); + this.database.close(); + } + } + + ///////////////////////////////////// exec sql ///////////////////////////////////////////////////// + + private void beginTransaction() { + if (allowTransaction) { + /** + * if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && database.isWriteAheadLoggingEnabled()) { + * database.beginTransactionNonExclusive(); + * } else { + */ + database.beginTransaction(); + } + } + + private void setTransactionSuccessful() { + if (allowTransaction) { + database.markAsCommit(); + } + } + + private void endTransaction() { + if (allowTransaction) { + database.endTransaction(); + } + } + + + @Override + public int executeUpdateDelete(SqlInfo sqlInfo) throws DbException { + Statement statement = null; + try { + statement = sqlInfo.buildStatement(database); + return statement.executeAndGetChanges(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.close(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public int executeUpdateDelete(String sql) throws DbException { + Statement statement = null; + try { + statement = database.buildStatement(sql); + return statement.executeAndGetChanges(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.close(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public void execNonQuery(SqlInfo sqlInfo) throws DbException { + Statement statement = null; + try { + statement = sqlInfo.buildStatement(database); + statement.execute(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.close(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public void execNonQuery(String sql) throws DbException { + try { + database.executeSql(sql); + } catch (Throwable e) { + throw new DbException(e); + } + } + + @Override + public ResultSet execQuery(SqlInfo sqlInfo) throws DbException { + try { + return database.querySql(sqlInfo.getSql(), sqlInfo.getBindArgsAsStrArray()); + } catch (Throwable e) { + throw new DbException(e); + } + } + + @Override + public ResultSet execQuery(String sql) throws DbException { + try { + return database.querySql(sql, null); + } catch (Throwable e) { + throw new DbException(e); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbModelSelector.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbModelSelector.java new file mode 100644 index 0000000000000000000000000000000000000000..ca37824567f77661623f7aab690aa0c5d6d5e8bf --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/DbModelSelector.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db; + +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.db.table.TableEntity; +import com.wordplat.quickstart.xutils.ex.DbException; + +import ohos.data.resultset.ResultSet; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-8-10 + * Time: 下午2:15 + * + * @since 2021-05-09 + */ +public final class DbModelSelector { + private String[] columnExpressions; + private String groupByColumnName; + private WhereBuilder having; + + private Selector selector; + + private DbModelSelector(TableEntity table) { + selector = Selector.from(table); + } + + /** + * DbModelSelector + * + * @param selector + * @param groupByColumnName + */ + protected DbModelSelector(Selector selector, String groupByColumnName) { + this.selector = selector; + this.groupByColumnName = groupByColumnName; + } + + /** + * DbModelSelector + * + * @param selector + * @param columnExpressions + */ + protected DbModelSelector(Selector selector, String[] columnExpressions) { + this.selector = selector; + this.columnExpressions = columnExpressions; + } + + static DbModelSelector from(TableEntity table) { + return new DbModelSelector(table); + } + + /** + * where + * + * @param whereBuilder + * @return DbModelSelector + */ + public DbModelSelector where(WhereBuilder whereBuilder) { + selector.where(whereBuilder); + return this; + } + + /** + * where + * + * @param columnName + * @param op + * @param value + * @return DbModelSelector + */ + public DbModelSelector where(String columnName, String op, Object value) { + selector.where(columnName, op, value); + return this; + } + + /** + * and + * + * @param columnName + * @param op + * @param value + * @return DbModelSelector + */ + public DbModelSelector and(String columnName, String op, Object value) { + selector.and(columnName, op, value); + return this; + } + + /** + * and + * + * @param where + * @return DbModelSelector + */ + public DbModelSelector and(WhereBuilder where) { + selector.and(where); + return this; + } + + /** + * or + * + * @param columnName + * @param op + * @param value + * @return DbModelSelector + */ + public DbModelSelector or(String columnName, String op, Object value) { + selector.or(columnName, op, value); + return this; + } + + /** + * or + * + * @param where + * @return DbModelSelector + */ + public DbModelSelector or(WhereBuilder where) { + selector.or(where); + return this; + } + + /** + * expr + * + * @param expr + * @return DbModelSelector + */ + public DbModelSelector expr(String expr) { + selector.expr(expr); + return this; + } + + /** + * groupBy + * + * @param columnName + * @return DbModelSelector + */ + public DbModelSelector groupBy(String columnName) { + this.groupByColumnName = columnName; + return this; + } + + /** + * having + * + * @param whereBuilder + * @return DbModelSelector + */ + public DbModelSelector having(WhereBuilder whereBuilder) { + this.having = whereBuilder; + return this; + } + + /** + * select + * + * @param columnExpressions + * @return DbModelSelector + */ + public DbModelSelector select(String... columnExpressions) { + this.columnExpressions = columnExpressions; + return this; + } + + /** + * orderBy + * + * @param columnName + * @return DbModelSelector + */ + public DbModelSelector orderBy(String columnName) { + selector.orderBy(columnName); + return this; + } + + /** + * orderBy + * + * @param columnName + * @param desc + * @return DbModelSelector + */ + public DbModelSelector orderBy(String columnName, boolean desc) { + selector.orderBy(columnName, desc); + return this; + } + + /** + * limit + * + * @param limit + * @return DbModelSelector + */ + public DbModelSelector limit(int limit) { + selector.limit(limit); + return this; + } + + /** + * DbModelSelector + * + * @param offset + * @return offset + */ + public DbModelSelector offset(int offset) { + selector.offset(offset); + return this; + } + + public TableEntity getTable() { + return selector.getTable(); + } + + /** + * findFirst + * + * @return DbModel + * @throws DbException + */ + public DbModel findFirst() throws DbException { + TableEntity table = selector.getTable(); + if (!table.tableIsExists()) { + return null; + } + + this.limit(1); + ResultSet cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + return CursorUtils.getDbModel(cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + /** + * findAll + * + * @return DbModel + * @throws DbException + */ + public List findAll() throws DbException { + TableEntity table = selector.getTable(); + if (!table.tableIsExists()) { + return null; + } + + List result = null; + + ResultSet cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + result = new ArrayList(); + while (cursor.goToNextRow()) { + DbModel entity = CursorUtils.getDbModel(cursor); + result.add(entity); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return result; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("SELECT "); + if (columnExpressions != null && columnExpressions.length > 0) { + for (String columnExpression : columnExpressions) { + result.append(columnExpression); + result.append(","); + } + result.deleteCharAt(result.length() - 1); + } else { + if (!TextUtils.isEmpty(groupByColumnName)) { + result.append(groupByColumnName); + } else { + result.append("*"); + } + } + result.append(" FROM ").append("\"").append(selector.getTable().getName()).append("\""); + WhereBuilder whereBuilder = selector.getWhereBuilder(); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + result.append(" WHERE ").append(whereBuilder.toString()); + } + if (!TextUtils.isEmpty(groupByColumnName)) { + result.append(" GROUP BY ").append("\"").append(groupByColumnName).append("\""); + if (having != null && having.getWhereItemSize() > 0) { + result.append(" HAVING ").append(having.toString()); + } + } + List orderByList = selector.getOrderByList(); + if (orderByList != null && orderByList.size() > 0) { + result.append(" ORDER BY "); + for (Selector.OrderBy orderBy : orderByList) { + result.append(orderBy.toString()).append(','); + } + result.deleteCharAt(result.length() - 1); + } + if (selector.getLimit() > 0) { + result.append(" LIMIT ").append(selector.getLimit()); + result.append(" OFFSET ").append(selector.getOffset()); + } + return result.toString(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/Selector.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/Selector.java new file mode 100644 index 0000000000000000000000000000000000000000..98d1f63ff1d632bbaf7a3b242dfef7fb1b07ce0c --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/Selector.java @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db; + +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.db.table.TableEntity; +import com.wordplat.quickstart.xutils.ex.DbException; + +import ohos.data.resultset.ResultSet; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-8-9 + * Time: 下午10:19 + * + * @since 2021-05-09 + */ +public final class Selector { + private final TableEntity table; + + private WhereBuilder whereBuilder; + private List orderByList; + private int limit = 0; + private int offset = 0; + + private Selector(TableEntity table) { + this.table = table; + } + + static Selector from(TableEntity table) { + return new Selector(table); + } + + /** + * where + * + * @param whereBuilder + * @return Selector + */ + public Selector where(WhereBuilder whereBuilder) { + this.whereBuilder = whereBuilder; + return this; + } + + /** + * where + * + * @param columnName + * @param op + * @param value + * @return Selector + */ + public Selector where(String columnName, String op, Object value) { + this.whereBuilder = WhereBuilder.b(columnName, op, value); + return this; + } + + /** + * and + * + * @param columnName + * @param op + * @param value + * @return Selector + */ + public Selector and(String columnName, String op, Object value) { + this.whereBuilder.and(columnName, op, value); + return this; + } + + /** + * and + * + * @param where + * @return Selector + */ + public Selector and(WhereBuilder where) { + this.whereBuilder.and(where); + return this; + } + + /** + * or + * + * @param columnName + * @param op + * @param value + * @return Selector + */ + public Selector or(String columnName, String op, Object value) { + this.whereBuilder.or(columnName, op, value); + return this; + } + + /** + * or + * + * @param where + * @return Selector + */ + public Selector or(WhereBuilder where) { + this.whereBuilder.or(where); + return this; + } + + /** + * expr + * + * @param expr + * @return Selector + */ + public Selector expr(String expr) { + if (this.whereBuilder == null) { + this.whereBuilder = WhereBuilder.b(); + } + this.whereBuilder.expr(expr); + return this; + } + + /** + * groupBy + * + * @param columnName + * @return DbModelSelector + */ + public DbModelSelector groupBy(String columnName) { + return new DbModelSelector(this, columnName); + } + + /** + * select + * + * @param columnExpressions + * @return DbModelSelector + */ + public DbModelSelector select(String... columnExpressions) { + return new DbModelSelector(this, columnExpressions); + } + + /** + * orderBy + * + * @param columnName + * @return Selector + */ + public Selector orderBy(String columnName) { + if (orderByList == null) { + orderByList = new ArrayList(5); + } + orderByList.add(new OrderBy(columnName)); + return this; + } + + /** + * orderBy + * + * @param columnName + * @param desc + * @return Selector + */ + public Selector orderBy(String columnName, boolean desc) { + if (orderByList == null) { + orderByList = new ArrayList(5); + } + orderByList.add(new OrderBy(columnName, desc)); + return this; + } + + /** + * limit + * + * @param limit + * @return Selector + */ + public Selector limit(int limit) { + this.limit = limit; + return this; + } + + /** + * offset + * + * @param offset + * @return Selector + */ + public Selector offset(int offset) { + this.offset = offset; + return this; + } + + public TableEntity getTable() { + return table; + } + + public WhereBuilder getWhereBuilder() { + return whereBuilder; + } + + public List getOrderByList() { + return orderByList; + } + + public int getLimit() { + return limit; + } + + public int getOffset() { + return offset; + } + + /** + * findFirst + * + * @return T + * @throws DbException + */ + public T findFirst() throws DbException { + if (!table.tableIsExists()) { + return null; + } + + this.limit(1); + ResultSet cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + return CursorUtils.getEntity(table, cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + /** + * findAll + * + * @return List + * @throws DbException + */ + public List findAll() throws DbException { + if (!table.tableIsExists()) { + return null; + } + + List result = null; + ResultSet cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + result = new ArrayList(); + while (cursor.goToNextRow()) { + T entity = CursorUtils.getEntity(table, cursor); + result.add(entity); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return result; + } + + /** + * count + * + * @return long + * @throws DbException + */ + public long count() throws DbException { + if (!table.tableIsExists()) { + return 0; + } + + DbModelSelector dmSelector = this.select("count(\"" + table.getId().getName() + "\") as count"); + DbModel firstModel = dmSelector.findFirst(); + if (firstModel != null) { + return firstModel.getLong("count", 0); + } + return 0; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("SELECT "); + result.append("*"); + result.append(" FROM ").append("\"").append(table.getName()).append("\""); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + result.append(" WHERE ").append(whereBuilder.toString()); + } + if (orderByList != null && orderByList.size() > 0) { + result.append(" ORDER BY "); + for (OrderBy orderBy : orderByList) { + result.append(orderBy.toString()).append(','); + } + result.deleteCharAt(result.length() - 1); + } + if (limit > 0) { + result.append(" LIMIT ").append(limit); + result.append(" OFFSET ").append(offset); + } + return result.toString(); + } + + public static class OrderBy { + private String columnName; + private boolean desc; + /** + * 排序条件, 默认ASC + * + * @param columnName + */ + public OrderBy(String columnName) { + this.columnName = columnName; + } + + /** + * 排序条件, 默认ASC + * + * @param columnName + * @param desc + */ + public OrderBy(String columnName, boolean desc) { + this.columnName = columnName; + this.desc = desc; + } + + @Override + public String toString() { + return "\"" + columnName + "\"" + (desc ? " DESC" : " ASC"); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Column.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Column.java new file mode 100644 index 0000000000000000000000000000000000000000..731b8db04bce58b8b5cd7ef3efa717ce57c34f9d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Column.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Column + * + * @since 2021-05-09 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + /** + * name + * + * @return String + */ + String name(); + + /** + * property + * + * @return String + */ + String property() default ""; + + /** + * isId + * + * @return boolean + */ + boolean isId() default false; + + /** + * autoGen + * + * @return boolean + */ + boolean autoGen() default true; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Table.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Table.java new file mode 100644 index 0000000000000000000000000000000000000000..5a7bf7a6e8de07718f1acf7d920ce24c8243b011 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/annotation/Table.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Table + * + * @since 2021-085-09 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + /** + * name + * + * @return String + */ + String name(); + + /** + * onCreated + * + * @return String + */ + String onCreated() default ""; +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/BooleanColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/BooleanColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..d40816dc9b2ce2a3e64e06910b62d0f8e9787243 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/BooleanColumnConverter.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class BooleanColumnConverter implements ColumnConverter { + @Override + public Boolean getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getInt(index) == 1; + } + + @Override + public Object fieldValue2DbValue(Boolean fieldValue) { + if (fieldValue == null) { + return null; + } + return fieldValue ? 1 : 0; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteArrayColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteArrayColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..906ed349e2eea7ec09a422848140f55c0cf6bbc8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteArrayColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class ByteArrayColumnConverter implements ColumnConverter { + @Override + public byte[] getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getBlob(index); + } + + @Override + public Object fieldValue2DbValue(byte[] fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.BLOB; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..91170a63d667edd2200ca4278b36683cf3cea9f5 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ByteColumnConverter.java @@ -0,0 +1,28 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class ByteColumnConverter implements ColumnConverter { + @Override + public Byte getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : (byte) cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Byte fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/CharColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/CharColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..0594f5b5b3d2eea4115d584b879583c5939a1506 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/CharColumnConverter.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class CharColumnConverter implements ColumnConverter { + @Override + public Character getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : (char) cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Character fieldValue) { + if (fieldValue == null) { + return null; + } + return (int) fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..cbfbf85db475b3c0fa6e8956acbf60e099c254b1 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverter.java @@ -0,0 +1,38 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午8:57 + * + * @since 2021-05-09 + */ +public interface ColumnConverter { + /** + * getFieldValue + * + * @param cursor + * @param index + * @return T + */ + T getFieldValue(final ResultSet cursor, int index); + + /** + * fieldValue2DbValue + * + * @param fieldValue + * @return Object + */ + Object fieldValue2DbValue(T fieldValue); + + /** + * getColumnDbType + * + * @return ColumnDbType + */ + ColumnDbType getColumnDbType(); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverterFactory.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..9bfdca4b009df15652a34e5d2a3f547a319b2561 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ColumnConverterFactory.java @@ -0,0 +1,128 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:27 + * + * @since 2021-05-09 + */ +public final class ColumnConverterFactory { + private static final ConcurrentHashMap HASH_MAP; + + private ColumnConverterFactory() { + } + + /** + * getColumnConverter + * + * @param columnType + * @return ColumnConverter + * @throws RuntimeException + */ + public static ColumnConverter getColumnConverter(Class columnType) { + ColumnConverter result = null; + if (HASH_MAP.containsKey(columnType.getName())) { + result = HASH_MAP.get(columnType.getName()); + } else if (ColumnConverter.class.isAssignableFrom(columnType)) { + try { + ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance(); + HASH_MAP.put(columnType.getName(), columnConverter); + result = columnConverter; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + if (result == null) { + throw new RuntimeException("Database Column Not Support: " + columnType.getName() + + ", please impl ColumnConverter or use ColumnConverterFactory#registerColumnConverter(...)"); + } + + return result; + } + + /** + * registerColumnConverter + * + * @param columnType + * @param columnConverter + */ + public static void registerColumnConverter(Class columnType, ColumnConverter columnConverter) { + HASH_MAP.put(columnType.getName(), columnConverter); + } + + /** + * isSupportColumnConverter + * + * @param columnType + * @return boolean + */ + public static boolean isSupportColumnConverter(Class columnType) { + if (HASH_MAP.containsKey(columnType.getName())) { + return true; + } else if (ColumnConverter.class.isAssignableFrom(columnType)) { + try { + ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance(); + HASH_MAP.put(columnType.getName(), columnConverter); + return true; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return false; + } + + static { + HASH_MAP = new ConcurrentHashMap(); + + BooleanColumnConverter booleanColumnConverter = new BooleanColumnConverter(); + HASH_MAP.put(boolean.class.getName(), booleanColumnConverter); + HASH_MAP.put(Boolean.class.getName(), booleanColumnConverter); + + ByteArrayColumnConverter byteArrayColumnConverter = new ByteArrayColumnConverter(); + HASH_MAP.put(byte[].class.getName(), byteArrayColumnConverter); + + ByteColumnConverter byteColumnConverter = new ByteColumnConverter(); + HASH_MAP.put(byte.class.getName(), byteColumnConverter); + HASH_MAP.put(Byte.class.getName(), byteColumnConverter); + + CharColumnConverter charColumnConverter = new CharColumnConverter(); + HASH_MAP.put(char.class.getName(), charColumnConverter); + HASH_MAP.put(Character.class.getName(), charColumnConverter); + + DateColumnConverter dateColumnConverter = new DateColumnConverter(); + HASH_MAP.put(Date.class.getName(), dateColumnConverter); + + DoubleColumnConverter doubleColumnConverter = new DoubleColumnConverter(); + HASH_MAP.put(double.class.getName(), doubleColumnConverter); + HASH_MAP.put(Double.class.getName(), doubleColumnConverter); + + FloatColumnConverter floatColumnConverter = new FloatColumnConverter(); + HASH_MAP.put(float.class.getName(), floatColumnConverter); + HASH_MAP.put(Float.class.getName(), floatColumnConverter); + + IntegerColumnConverter integerColumnConverter = new IntegerColumnConverter(); + HASH_MAP.put(int.class.getName(), integerColumnConverter); + HASH_MAP.put(Integer.class.getName(), integerColumnConverter); + + LongColumnConverter longColumnConverter = new LongColumnConverter(); + HASH_MAP.put(long.class.getName(), longColumnConverter); + HASH_MAP.put(Long.class.getName(), longColumnConverter); + + ShortColumnConverter shortColumnConverter = new ShortColumnConverter(); + HASH_MAP.put(short.class.getName(), shortColumnConverter); + HASH_MAP.put(Short.class.getName(), shortColumnConverter); + + SqlDateColumnConverter sqlDateColumnConverter = new SqlDateColumnConverter(); + HASH_MAP.put(java.sql.Date.class.getName(), sqlDateColumnConverter); + + StringColumnConverter stringColumnConverter = new StringColumnConverter(); + HASH_MAP.put(String.class.getName(), stringColumnConverter); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DateColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DateColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..980108f11ddeb5e542ae099ebd390b0b5982bb4c --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DateColumnConverter.java @@ -0,0 +1,34 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +import java.util.Date; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class DateColumnConverter implements ColumnConverter { + @Override + public Date getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : new Date(cursor.getLong(index)); + } + + @Override + public Object fieldValue2DbValue(Date fieldValue) { + if (fieldValue == null) { + return null; + } + return fieldValue.getTime(); + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DoubleColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DoubleColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..a6cdb3bfd7ac200cc4b99c4f84f3c2226f8e07bd --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/DoubleColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class DoubleColumnConverter implements ColumnConverter { + @Override + public Double getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getDouble(index); + } + + @Override + public Object fieldValue2DbValue(Double fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.REAL; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/FloatColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/FloatColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..4cd8a3a02af13d50911f82cd4ef616de00952de6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/FloatColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class FloatColumnConverter implements ColumnConverter { + @Override + public Float getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getFloat(index); + } + + @Override + public Object fieldValue2DbValue(Float fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.REAL; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/IntegerColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/IntegerColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..48116a714b439d51c82969a63441277347d1144b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/IntegerColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class IntegerColumnConverter implements ColumnConverter { + @Override + public Integer getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Integer fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/LongColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/LongColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..d262f3f4ff1fb7b11970b7e7b3c3378c71b124a4 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/LongColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class LongColumnConverter implements ColumnConverter { + @Override + public Long getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getLong(index); + } + + @Override + public Object fieldValue2DbValue(Long fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ShortColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ShortColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..1189588969777c7c0f64b4971f1078456ad90647 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/ShortColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class ShortColumnConverter implements ColumnConverter { + @Override + public Short getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getShort(index); + } + + @Override + public Object fieldValue2DbValue(Short fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/SqlDateColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/SqlDateColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..c52abd83c87bdbc79dd3b885d17d748cedfb7c1b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/SqlDateColumnConverter.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class SqlDateColumnConverter implements ColumnConverter { + @Override + public java.sql.Date getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : new java.sql.Date(cursor.getLong(index)); + } + + @Override + public Object fieldValue2DbValue(java.sql.Date fieldValue) { + if (fieldValue == null) { + return null; + } + return fieldValue.getTime(); + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/StringColumnConverter.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/StringColumnConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..9648f9903ae3dfa808d6da70b735d3cdb6a11932 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/converter/StringColumnConverter.java @@ -0,0 +1,29 @@ +package com.wordplat.quickstart.xutils.db.converter; + +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + * + * @since 2021-05-09 + */ +public class StringColumnConverter implements ColumnConverter { + @Override + public String getFieldValue(final ResultSet cursor, int index) { + return cursor.isColumnNull(index) ? null : cursor.getString(index); + } + + @Override + public Object fieldValue2DbValue(String fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.TEXT; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/ColumnDbType.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/ColumnDbType.java new file mode 100644 index 0000000000000000000000000000000000000000..8e6b27c20633971a09634cc3a3ac3b6c7f7fbda6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/ColumnDbType.java @@ -0,0 +1,24 @@ +package com.wordplat.quickstart.xutils.db.sqlite; + +/** + * Created by wyouflf on 14-2-20. + * + * @since 2021-05-09 + */ +public enum ColumnDbType { + /** + * INTEGER("INTEGER"), REAL("REAL"), TEXT("TEXT"), BLOB("BLOB"); + */ + INTEGER("INTEGER"), REAL("REAL"), TEXT("TEXT"), BLOB("BLOB"); + + private String value; + + ColumnDbType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfo.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..0e2e39a9bfdafadcc701c31b0cb93787670a2e16 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfo.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.sqlite; + +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverter; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverterFactory; +import com.wordplat.quickstart.xutils.db.table.ColumnUtils; + +import ohos.data.rdb.RdbStore; +import ohos.data.rdb.Statement; + +import java.util.ArrayList; +import java.util.List; + +/** + * SqlInfo + * + * @since 2021-05-09 + */ +public final class SqlInfo { + private String sql; + private List bindArgs; + + /** + * SqlInfo + */ + public SqlInfo() { + } + + /** + * SqlInfo + * + * @param sql + */ + public SqlInfo(String sql) { + this.sql = sql; + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + /** + * addBindArg + * + * @param kv + */ + public void addBindArg(KeyValue kv) { + if (bindArgs == null) { + bindArgs = new ArrayList(); + } + bindArgs.add(kv); + } + + /** + * addBindArgs + * + * @param bindArgs + */ + public void addBindArgs(List bindArgs) { + if (this.bindArgs == null) { + this.bindArgs = bindArgs; + } else { + this.bindArgs.addAll(bindArgs); + } + } + + /** + * buildStatement + * + * @param database + * @return Statement + */ + @SuppressWarnings("unchecked") + public Statement buildStatement(RdbStore database) { + Statement result = database.buildStatement(sql); + if (bindArgs != null) { + for (int i = 1; i < bindArgs.size() + 1; i++) { + KeyValue kv = bindArgs.get(i - 1); + if (kv.value == null) { + result.setNull(i); + continue; + } + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(kv.value.getClass()); + Object value = converter.fieldValue2DbValue(kv.value); + ColumnDbType type = converter.getColumnDbType(); + switch (type) { + case INTEGER: + result.setLong(i, ((Number) value).longValue()); + break; + case REAL: + result.setDouble(i, ((Number) value).doubleValue()); + break; + case TEXT: + result.setString(i, value.toString()); + break; + case BLOB: + result.setBlob(i, (byte[]) value); + break; + default: + result.setNull(i); + break; + } // end switch + } + } + return result; + } + + /** + * getBindArgs + * + * @return Object + */ + public Object[] getBindArgs() { + Object[] result = null; + if (bindArgs != null) { + result = new Object[bindArgs.size()]; + for (int i = 0; i < bindArgs.size(); i++) { + result[i] = ColumnUtils.convert2DbValueIfNeeded(bindArgs.get(i).value); + } + } + return result; + } + + /** + * getBindArgsAsStrArray + * + * @return String + */ + public String[] getBindArgsAsStrArray() { + String[] result = null; + if (bindArgs != null) { + result = new String[bindArgs.size()]; + for (int i = 0; i < bindArgs.size(); i++) { + Object value = ColumnUtils.convert2DbValueIfNeeded(bindArgs.get(i).value); + result[i] = value == null ? null : value.toString(); + } + } + return result; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfoBuilder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfoBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..4611be13a75bebe80c6042a987c5c26d7b3ac338 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/SqlInfoBuilder.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.sqlite; + +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.db.table.ColumnEntity; +import com.wordplat.quickstart.xutils.db.table.TableEntity; +import com.wordplat.quickstart.xutils.ex.DbException; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Build "insert", "replace",,"update", "delete" and "create" sql. + * + * @since 2021-05-09 + */ +public final class SqlInfoBuilder { + private static final ConcurrentHashMap, String> INSERT_SQL_CACHE = + new ConcurrentHashMap, String>(); + private static final ConcurrentHashMap, String> REPLACE_SQL_CACHE = + new ConcurrentHashMap, String>(); + + private SqlInfoBuilder() { + } + + /** + * buildInsertSqlInfo + * + * @param table + * @param entity + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildInsertSqlInfo(TableEntity table, Object entity) throws DbException { + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) { + return null; + } + + SqlInfo result = new SqlInfo(); + String sql = INSERT_SQL_CACHE.get(table); + if (sql == null) { + StringBuilder builder = new StringBuilder(); + builder.append("INSERT INTO "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ("); + for (KeyValue kv : keyValueList) { + builder.append("\"").append(kv.key).append("\"").append(','); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(") VALUES ("); + + int length = keyValueList.size(); + for (int i = 0; i < length; i++) { + builder.append("?,"); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(")"); + + sql = builder.toString(); + result.setSql(sql); + result.addBindArgs(keyValueList); + INSERT_SQL_CACHE.put(table, sql); + } else { + result.setSql(sql); + result.addBindArgs(keyValueList); + } + + return result; + } + + /** + * buildReplaceSqlInfo + * + * @param table + * @param entity + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildReplaceSqlInfo(TableEntity table, Object entity) throws DbException { + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) { + return null; + } + + SqlInfo result = new SqlInfo(); + String sql = REPLACE_SQL_CACHE.get(table); + if (sql == null) { + StringBuilder builder = new StringBuilder(); + builder.append("REPLACE INTO "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ("); + for (KeyValue kv : keyValueList) { + builder.append("\"").append(kv.key).append("\"").append(','); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(") VALUES ("); + + int length = keyValueList.size(); + for (int i = 0; i < length; i++) { + builder.append("?,"); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(")"); + + sql = builder.toString(); + result.setSql(sql); + result.addBindArgs(keyValueList); + REPLACE_SQL_CACHE.put(table, sql); + } else { + result.setSql(sql); + result.addBindArgs(keyValueList); + } + + return result; + } + + /** + * buildDeleteSqlInfo + * + * @param table + * @param entity + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildDeleteSqlInfo(TableEntity table, Object entity) throws DbException { + SqlInfo result = new SqlInfo(); + + ColumnEntity id = table.getId(); + Object idValue = id.getColumnValue(entity); + + if (idValue == null) { + throw new DbException("this entity id value is null"); + } + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + + return result; + } + + /** + * buildDeleteSqlInfoById + * + * @param table + * @param idValue + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildDeleteSqlInfoById(TableEntity table, Object idValue) throws DbException { + SqlInfo result = new SqlInfo(); + + ColumnEntity id = table.getId(); + + if (idValue == null) { + throw new DbException("this entity id value is null"); + } + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + + return result; + } + + /** + * buildDeleteSqlInfo + * + * @param table + * @param whereBuilder + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildDeleteSqlInfo(TableEntity table, WhereBuilder whereBuilder) throws DbException { + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + builder.append(" WHERE ").append(whereBuilder.toString()); + } + + return new SqlInfo(builder.toString()); + } + + /** + * buildUpdateSqlInfo + * + * @param table + * @param entity + * @param updateColumnNames + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildUpdateSqlInfo(TableEntity table, Object entity, String... updateColumnNames) throws DbException { + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) { + return null; + } + + HashSet updateColumnNameSet = null; + if (updateColumnNames != null && updateColumnNames.length > 0) { + updateColumnNameSet = new HashSet(updateColumnNames.length); + Collections.addAll(updateColumnNameSet, updateColumnNames); + } + + ColumnEntity id = table.getId(); + Object idValue = id.getColumnValue(entity); + + if (idValue == null) { + throw new DbException("this entity id value is null"); + } + + SqlInfo result = new SqlInfo(); + StringBuilder builder = new StringBuilder("UPDATE "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" SET "); + for (KeyValue kv : keyValueList) { + if (updateColumnNameSet == null || updateColumnNameSet.contains(kv.key)) { + builder.append("\"").append(kv.key).append("\"").append("=?,"); + result.addBindArg(kv); + } + } + builder.deleteCharAt(builder.length() - 1); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + return result; + } + + /** + * buildUpdateSqlInfo + * + * @param table + * @param whereBuilder + * @param nameValuePairs + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildUpdateSqlInfo(TableEntity table, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException { + + if (nameValuePairs == null || nameValuePairs.length == 0) { + return null; + } + + SqlInfo result = new SqlInfo(); + StringBuilder builder = new StringBuilder("UPDATE "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" SET "); + for (KeyValue kv : nameValuePairs) { + builder.append("\"").append(kv.key).append("\"").append("=?,"); + result.addBindArg(kv); + } + builder.deleteCharAt(builder.length() - 1); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + builder.append(" WHERE ").append(whereBuilder.toString()); + } + + result.setSql(builder.toString()); + return result; + } + + /** + * buildCreateTableSqlInfo + * + * @param table + * @return SqlInfo + * @throws DbException + */ + public static SqlInfo buildCreateTableSqlInfo(TableEntity table) throws DbException { + ColumnEntity id = table.getId(); + + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ( "); + + if (id.isAutoId()) { + builder.append("\"").append(id.getName()).append("\"").append(" INTEGER PRIMARY KEY AUTOINCREMENT, "); + } else { + builder.append("\"").append(id.getName()).append("\"").append(id.getColumnDbType()).append(" PRIMARY KEY, "); + } + + Collection columns = table.getColumnMap().values(); + for (ColumnEntity column : columns) { + if (column.isId()) { + continue; + } + builder.append("\"").append(column.getName()).append("\""); + builder.append(' ').append(column.getColumnDbType()); + builder.append(' ').append(column.getProperty()); + builder.append(','); + } + + builder.deleteCharAt(builder.length() - 1); + builder.append(" )"); + return new SqlInfo(builder.toString()); + } + + /** + * entity2KeyValueList + * + * @param table + * @param entity + * @return List + */ + public static List entity2KeyValueList(TableEntity table, Object entity) { + Collection columns = table.getColumnMap().values(); + List keyValueList = new ArrayList(columns.size()); + for (ColumnEntity column : columns) { + KeyValue kv = column2KeyValue(entity, column); + if (kv != null) { + keyValueList.add(kv); + } + } + + return keyValueList; + } + + private static KeyValue column2KeyValue(Object entity, ColumnEntity column) { + if (column.isAutoId()) { + return null; + } + + String key = column.getName(); + Object value = column.getFieldValue(entity); + return new KeyValue(key, value); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/WhereBuilder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/WhereBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..25a58b78babbd8b684fcbcd8274af48758a59fe4 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/sqlite/WhereBuilder.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.sqlite; + +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.db.table.ColumnUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-7-29 + * Time: 上午9:35 + * + * @since 2021-05-09 + */ +public class WhereBuilder { + private final List whereItems; + + private WhereBuilder() { + this.whereItems = new ArrayList(); + } + + /** + * b + * + * @return WhereBuilder + */ + public static WhereBuilder b() { + return new WhereBuilder(); + } + + /** + * b + * + * @param columnName + * @param op + * @param value + * @return WhereBuilder + */ + public static WhereBuilder b(String columnName, String op, Object value) { + WhereBuilder result = new WhereBuilder(); + result.appendCondition(null, columnName, op, value); + return result; + } + + /** + * and + * + * @param columnName + * @param op + * @param value + * @return WhereBuilder + */ + public WhereBuilder and(String columnName, String op, Object value) { + appendCondition(whereItems.size() == 0 ? null : "AND", columnName, op, value); + return this; + } + + /** + * and + * + * @param where + * @return WhereBuilder + */ + public WhereBuilder and(WhereBuilder where) { + String condition = whereItems.size() == 0 ? " " : "AND "; + return expr(condition + "(" + where.toString() + ")"); + } + + /** + * or + * + * @param columnName + * @param op + * @param value + * @return WhereBuilder + */ + public WhereBuilder or(String columnName, String op, Object value) { + appendCondition(whereItems.size() == 0 ? null : "OR", columnName, op, value); + return this; + } + + /** + * or + * + * @param where + * @return WhereBuilder + */ + public WhereBuilder or(WhereBuilder where) { + String condition = whereItems.size() == 0 ? " " : "OR "; + return expr(condition + "(" + where.toString() + ")"); + } + + /** + * expr + * + * @param expr + * @return WhereBuilder + */ + public WhereBuilder expr(String expr) { + whereItems.add(" " + expr); + return this; + } + + /** + * getWhereItemSize + * + * @return int + */ + public int getWhereItemSize() { + return whereItems.size(); + } + + /** + * toString + * + * @return String + */ + @Override + public String toString() { + if (whereItems.size() == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (String item : whereItems) { + sb.append(item); + } + return sb.toString(); + } + + /** + * appendCondition + * + * @param conj + * @param columnName + * @param op + * @param value + */ + private void appendCondition(String conj, String columnName, String op, Object value) { + StringBuilder builder = new StringBuilder(); + + if (whereItems.size() > 0) { + builder.append(" "); + } + + if (!TextUtils.isEmpty(conj)) { + builder.append(conj).append(" "); + } + + builder.append("\"").append(columnName).append("\""); + + // convert op + if ("!=".equals(op)) { + op = "<>"; + } else if ("==".equals(op)) { + op = "="; + } + + if (value == null) { + if ("=".equals(op)) { + builder.append(" IS NULL"); + } else if ("<>".equals(op)) { + builder.append(" IS NOT NULL"); + } else { + builder.append(" ").append(op).append(" NULL"); + } + } else { + builder.append(" ").append(op).append(" "); + + if ("IN".equalsIgnoreCase(op)) { + Iterable items = null; + if (value instanceof Iterable) { + items = (Iterable) value; + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + List arrayList = new ArrayList(len); + for (int i = 0; i < len; i++) { + arrayList.add(Array.get(value, i)); + } + items = arrayList; + } + if (items != null) { + StringBuilder inSb = new StringBuilder("("); + for (Object item : items) { + Object itemColValue = ColumnUtils.convert2DbValueIfNeeded(item); + if (ColumnUtils.isTextColumnDbType(itemColValue)) { + String valueStr = ColumnUtils.convert2SafeExpr(itemColValue); + inSb.append("'").append(valueStr).append("'"); + } else { + inSb.append(itemColValue); + } + inSb.append(","); + } + if (inSb.length() > 1) { + inSb.deleteCharAt(inSb.length() - 1); + } + inSb.append(")"); + builder.append(inSb.toString()); + } else { + throw new IllegalArgumentException("value must be an Array or an Iterable."); + } + } else if ("BETWEEN".equalsIgnoreCase(op)) { + Iterable items = null; + if (value instanceof Iterable) { + items = (Iterable) value; + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + List arrayList = new ArrayList(len); + for (int i = 0; i < len; i++) { + arrayList.add(Array.get(value, i)); + } + items = arrayList; + } + if (items != null) { + Iterator iterator = items.iterator(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("value must contains tow items."); + } + Object start = iterator.next(); + if (!iterator.hasNext()) { + throw new IllegalArgumentException("value must contains tow items."); + } + Object end = iterator.next(); + + Object startColValue = ColumnUtils.convert2DbValueIfNeeded(start); + Object endColValue = ColumnUtils.convert2DbValueIfNeeded(end); + + if (ColumnUtils.isTextColumnDbType(startColValue)) { + String startStr = ColumnUtils.convert2SafeExpr(startColValue); + String endStr = ColumnUtils.convert2SafeExpr(endColValue); + builder.append("'").append(startStr).append("'"); + builder.append(" AND "); + builder.append("'").append(endStr).append("'"); + } else { + builder.append(startColValue); + builder.append(" AND "); + builder.append(endColValue); + } + } else { + throw new IllegalArgumentException("value must be an Array or an Iterable."); + } + } else { + value = ColumnUtils.convert2DbValueIfNeeded(value); + if (ColumnUtils.isTextColumnDbType(value)) { + String valueStr = ColumnUtils.convert2SafeExpr(value); + builder.append("'").append(valueStr).append("'"); + } else { + builder.append(value); + } + } + } + whereItems.add(builder.toString()); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnEntity.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..ec445862053e6511252ebe8ae3dca234ef65481a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnEntity.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.db.annotation.Column; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverter; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverterFactory; +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import ohos.data.resultset.ResultSet; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class ColumnEntity { + protected final String name; + private final String property; + private final boolean isId; + private final boolean isAutoId; + + protected final Method getMethod; + protected final Method setMethod; + + protected final Field columnField; + protected final ColumnConverter columnConverter; + + /* package */ ColumnEntity(Class entityType, Field field, Column column) { + field.setAccessible(true); + + this.columnField = field; + this.name = column.name(); + this.property = column.property(); + this.isId = column.isId(); + + Class fieldType = field.getType(); + this.isAutoId = this.isId && column.autoGen() && ColumnUtils.isAutoIdType(fieldType); + this.columnConverter = ColumnConverterFactory.getColumnConverter(fieldType); + + + this.getMethod = ColumnUtils.findGetMethod(entityType, field); + if (this.getMethod != null && !this.getMethod.isAccessible()) { + this.getMethod.setAccessible(true); + } + this.setMethod = ColumnUtils.findSetMethod(entityType, field); + if (this.setMethod != null && !this.setMethod.isAccessible()) { + this.setMethod.setAccessible(true); + } + } + + /** + * setValueFromCursor + * + * @param entity + * @param cursor + * @param index + */ + public void setValueFromCursor(Object entity, ResultSet cursor, int index) { + Object value = columnConverter.getFieldValue(cursor, index); + if (value == null) { + return; + } + + if (setMethod != null) { + try { + setMethod.invoke(entity, value); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + this.columnField.set(entity, value); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + + /** + * getColumnValue + * + * @param entity + * @return + */ + @SuppressWarnings("unchecked") + public Object getColumnValue(Object entity) { + Object fieldValue = getFieldValue(entity); + if (this.isAutoId && (fieldValue.equals(0L) || fieldValue.equals(0))) { + return null; + } + return columnConverter.fieldValue2DbValue(fieldValue); + } + + /** + * setAutoIdValue + * + * @param entity + * @param value + */ + public void setAutoIdValue(Object entity, long value) { + Object idValue = value; + if (ColumnUtils.isInteger(columnField.getType())) { + idValue = (int) value; + } + + if (setMethod != null) { + try { + setMethod.invoke(entity, idValue); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + this.columnField.set(entity, idValue); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + + /** + * getFieldValue + * + * @param entity + * @return Object + */ + public Object getFieldValue(Object entity) { + Object fieldValue = null; + if (entity != null) { + if (getMethod != null) { + try { + fieldValue = getMethod.invoke(entity); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + fieldValue = this.columnField.get(entity); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + return fieldValue; + } + + public String getName() { + return name; + } + + public String getProperty() { + return property; + } + + public boolean isId() { + return isId; + } + + public boolean isAutoId() { + return isAutoId; + } + + public Field getColumnField() { + return columnField; + } + + public ColumnConverter getColumnConverter() { + return columnConverter; + } + + public ColumnDbType getColumnDbType() { + return columnConverter.getColumnDbType(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnUtils.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..935b91ffc32893db6f1b42099be2dd5b0ce2993d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/ColumnUtils.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverter; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverterFactory; +import com.wordplat.quickstart.xutils.db.sqlite.ColumnDbType; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; + +/** + * ColumnUtils + * + * @since 2021-05-09 + */ +public final class ColumnUtils { + private static final HashSet> BOOLEAN_TYPES = new HashSet>(2); + private static final HashSet> INTEGER_TYPES = new HashSet>(2); + private static final HashSet> AUTO_INCREMENT_TYPES = new HashSet>(4); + + private ColumnUtils() { + } + + static { + BOOLEAN_TYPES.add(boolean.class); + BOOLEAN_TYPES.add(Boolean.class); + + INTEGER_TYPES.add(int.class); + INTEGER_TYPES.add(Integer.class); + + AUTO_INCREMENT_TYPES.addAll(INTEGER_TYPES); + AUTO_INCREMENT_TYPES.add(long.class); + AUTO_INCREMENT_TYPES.add(Long.class); + } + + /** + * isAutoIdType + * + * @param fieldType + * @return boolean + */ + public static boolean isAutoIdType(Class fieldType) { + return AUTO_INCREMENT_TYPES.contains(fieldType); + } + + /** + * isInteger + * + * @param fieldType + * @return boolean + */ + public static boolean isInteger(Class fieldType) { + return INTEGER_TYPES.contains(fieldType); + } + + /** + * isBoolean + * + * @param fieldType + * @return boolean + */ + public static boolean isBoolean(Class fieldType) { + return BOOLEAN_TYPES.contains(fieldType); + } + + /** + * isTextColumnDbType + * + * @param value + * @return boolean + */ + public static boolean isTextColumnDbType(Object value) { + if (value == null) { + return false; + } + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(value.getClass()); + return converter != null && ColumnDbType.TEXT.equals(converter.getColumnDbType()); + } + + /** + * convert2SafeExpr + * + * @param value + * @return String + */ + public static String convert2SafeExpr(Object value) { + String result = String.valueOf(value); + if (result.indexOf('\'') != -1) { + result = result.replace("'", "''"); + } + return result; + } + + /** + * convert2DbValueIfNeeded + * + * @param value + * @return Object + */ + @SuppressWarnings("unchecked") + public static Object convert2DbValueIfNeeded(final Object value) { + Object result = value; + if (value != null) { + Class valueType = value.getClass(); + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(valueType); + result = converter.fieldValue2DbValue(value); + } + return result; + } + + static Method findGetMethod(Class entityType, Field field) { + if (Object.class.equals(entityType)) { + return null; + } + String fieldName = field.getName(); + Method getMethod = null; + if (isBoolean(field.getType())) { + getMethod = findBooleanGetMethod(entityType, fieldName); + } + if (getMethod == null) { + String methodName = "get" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + try { + getMethod = entityType.getDeclaredMethod(methodName); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + } + + if (getMethod == null) { + return findGetMethod(entityType.getSuperclass(), field); + } + return getMethod; + } + + static Method findSetMethod(Class entityType, Field field) { + if (Object.class.equals(entityType)) { + return null; + } + String fieldName = field.getName(); + Class fieldType = field.getType(); + Method setMethod = null; + if (isBoolean(fieldType)) { + setMethod = findBooleanSetMethod(entityType, fieldName, fieldType); + } + if (setMethod == null) { + String methodName = "set" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + try { + setMethod = entityType.getDeclaredMethod(methodName, fieldType); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + } + + if (setMethod == null) { + return findSetMethod(entityType.getSuperclass(), field); + } + return setMethod; + } + + private static Method findBooleanGetMethod(Class entityType, final String fieldName) { + String methodName = null; + if (fieldName.startsWith("is")) { + methodName = fieldName; + } else { + methodName = "is" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + } + try { + return entityType.getDeclaredMethod(methodName); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + return null; + } + + private static Method findBooleanSetMethod(Class entityType, final String fieldName, Class fieldType) { + String methodName = null; + if (fieldName.startsWith("is") && fieldName.length() > 2) { + methodName = "set" + fieldName.substring(2, 3).toUpperCase(); + if (fieldName.length() > 3) { + methodName += fieldName.substring(3); + } + } else { + methodName = "set" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + } + try { + return entityType.getDeclaredMethod(methodName, fieldType); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + return null; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbBase.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbBase.java new file mode 100644 index 0000000000000000000000000000000000000000..bd44757a554bb60414703cae35790268b7a61ce3 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbBase.java @@ -0,0 +1,113 @@ +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.ex.DbException; + +import ohos.data.resultset.ResultSet; + +import java.util.HashMap; + +/** + * DbManager基类, 包含表结构的基本操作. + * Created by wyouflf on 16/1/22. + * + * @since 2021-05-09 + */ +public abstract class DbBase implements DbManager { + + private final HashMap, TableEntity> tableMap = new HashMap, TableEntity>(); + + @Override + @SuppressWarnings("unchecked") + public TableEntity getTable(Class entityType) throws DbException { + synchronized (tableMap) { + TableEntity table = (TableEntity) tableMap.get(entityType); + if (table == null) { + try { + table = new TableEntity(this, entityType); + } catch (DbException ex) { + throw ex; + } catch (Throwable ex) { + throw new DbException(ex); + } + tableMap.put(entityType, table); + } + + return table; + } + } + + @Override + public void dropTable(Class entityType) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) { + return; + } + execNonQuery("DROP TABLE \"" + table.getName() + "\""); + table.setTableCheckedStatus(false); + this.removeTable(entityType); + } + + @Override + public void dropDb() throws DbException { + ResultSet cursor = execQuery("SELECT name FROM sqlite_master WHERE type='table' AND name<>'sqlite_sequence'"); + if (cursor != null) { + try { + while (cursor.goToNextRow()) { + try { + String tableName = cursor.getString(0); + execNonQuery("DROP TABLE " + tableName); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + + synchronized (tableMap) { + for (TableEntity table : tableMap.values()) { + table.setTableCheckedStatus(false); + } + tableMap.clear(); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + } + + @Override + public void addColumn(Class entityType, String column) throws DbException { + TableEntity table = this.getTable(entityType); + ColumnEntity col = table.getColumnMap().get(column); + if (col != null) { + /** + * // 不需要添加, 表创建时会自动添加 + */ + if (!table.tableIsExists()) { + return; + } + StringBuilder builder = new StringBuilder(); + builder.append("ALTER TABLE ").append("\"").append(table.getName()).append("\""). + append(" ADD COLUMN ").append("\"").append(col.getName()).append("\""). + append(" ").append(col.getColumnDbType()). + append(" ").append(col.getProperty()); + execNonQuery(builder.toString()); + } else { + throw new DbException("the column(" + column + ") is not defined in table: " + table.getName()); + } + } + + /** + * removeTable + * + * @param entityType + */ + protected void removeTable(Class entityType) { + synchronized (tableMap) { + tableMap.remove(entityType); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbModel.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbModel.java new file mode 100644 index 0000000000000000000000000000000000000000..58f150efb8034363833b3b42d578bfe9a6c2fe8d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/DbModel.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.common.util.TextUtils; + +import java.util.Date; +import java.util.HashMap; + +/** + * DbModel + * + * @since 2021-05-09 + */ +public final class DbModel { + private final HashMap dataMap = new HashMap(); + /** + * getString + * + * @param columnName + * @return String + */ + public String getString(String columnName) { + return dataMap.get(columnName); + } + + /** + * getInt + * + * @param columnName + * @param defaultValue + * @return getInt + */ + public int getInt(String columnName, int defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Integer.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + /** + * getBoolean + * + * @param columnName + * @return getBoolean + */ + public boolean getBoolean(String columnName) { + String value = dataMap.get(columnName); + if (value != null) { + return value.length() == 1 ? "1".equals(value) : Boolean.valueOf(value); + } + return false; + } + + /** + * getDouble + * + * @param columnName + * @param defaultValue + * @return getDouble + */ + public double getDouble(String columnName, double defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Double.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + /** + * getFloat + * + * @param columnName + * @param defaultValue + * @return getFloat + */ + public float getFloat(String columnName, float defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Float.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + /** + * getLong + * + * @param columnName + * @param defaultValue + * @return getLong + */ + public long getLong(String columnName, long defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Long.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + /** + * getDate + * + * @param columnName + * @param defaultTime + * @return getDate + */ + public Date getDate(String columnName, long defaultTime) { + return new Date(getLong(columnName, defaultTime)); + } + + /** + * getSqlDate + * + * @param columnName + * @param defaultTime + * @return getSqlDate + */ + public java.sql.Date getSqlDate(String columnName, long defaultTime) { + return new java.sql.Date(getLong(columnName, defaultTime)); + } + + /** + * add + * + * @param columnName + * @param valueStr + */ + public void add(String columnName, String valueStr) { + dataMap.put(columnName, valueStr); + } + + /** + * getDataMap + * + * @return HashMap + */ + public HashMap getDataMap() { + return dataMap; + } + + /** + * isEmpty + * + * @param columnName + * @return boolean + */ + public boolean isEmpty(String columnName) { + return TextUtils.isEmpty(dataMap.get(columnName)); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableEntity.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..a35096cd586bce5427d94d19641a1a43fa7948dc --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableEntity.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.db.annotation.Table; +import com.wordplat.quickstart.xutils.db.sqlite.SqlInfo; +import com.wordplat.quickstart.xutils.db.sqlite.SqlInfoBuilder; +import com.wordplat.quickstart.xutils.ex.DbException; + +import ohos.data.resultset.ResultSet; + +import java.lang.reflect.Constructor; +import java.util.LinkedHashMap; + +/** + * TableEntity + * + * @param + * @since 2021-05-09 + */ +public final class TableEntity { + private final DbManager db; + private final String name; + private final String onCreated; + private final Class entityType; + private final Constructor constructor; + private ColumnEntity id; + private volatile Boolean tableCheckedStatus; + private final Object lock = new Object(); // private final lock object + + /** + * key: columnName + */ + private final LinkedHashMap columnMap; + + TableEntity(DbManager db, Class entityType) throws Throwable { + this.db = db; + this.entityType = entityType; + + Table table = entityType.getAnnotation(Table.class); + if (table == null) { + throw new DbException("missing @Table on " + entityType.getName()); + } + this.name = table.name(); + this.onCreated = table.onCreated(); + + try { + this.constructor = entityType.getConstructor(); + this.constructor.setAccessible(true); + } catch (Throwable ex) { + throw new DbException("missing no-argument constructor for the table: " + this.name); + } + + this.columnMap = TableUtils.findColumnMap(entityType); + for (ColumnEntity column : columnMap.values()) { + if (column.isId()) { + this.id = column; + break; + } + } + } + + /** + * createEntity + * + * @return T + * @throws Throwable + */ + public T createEntity() throws Throwable { + return this.constructor.newInstance(); + } + + /** + * tableIsExists + * + * @return boolean + * @throws DbException + */ + public boolean tableIsExists() throws DbException { + return tableIsExists(false); + } + + /** + * tableIsExists + * + * @param forceCheckFromDb + * @return boolean + * @throws DbException + */ + public boolean tableIsExists(boolean forceCheckFromDb) throws DbException { + if (tableCheckedStatus != null && (tableCheckedStatus || !forceCheckFromDb)) { + return tableCheckedStatus; + } + + ResultSet cursor = db.execQuery("SELECT COUNT(*) AS c FROM sqlite_master " + + "WHERE type='table' AND name='" + name + "'"); + if (cursor != null) { + try { + if (cursor.goToNextRow()) { + int count = cursor.getInt(0); + if (count > 0) { + tableCheckedStatus = true; + return tableCheckedStatus; + } + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + + tableCheckedStatus = false; + return tableCheckedStatus; + } + + /** + * createTableIfNotExists + * + * @throws DbException + */ + public void createTableIfNotExists() throws DbException { + if (tableCheckedStatus != null && tableCheckedStatus) { + return; + } + synchronized (entityType) { + if (!this.tableIsExists(true)) { + SqlInfo sqlInfo = SqlInfoBuilder.buildCreateTableSqlInfo(this); + db.execNonQuery(sqlInfo); + tableCheckedStatus = true; + + if (!TextUtils.isEmpty(onCreated)) { + db.execNonQuery(onCreated); + } + + DbManager.TableCreateListener listener = db.getDaoConfig().getTableCreateListener(); + if (listener != null) { + try { + listener.onTableCreated(db, this); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } + + public DbManager getDb() { + return db; + } + + public String getName() { + return name; + } + + public String getOnCreated() { + return onCreated; + } + + public ColumnEntity getId() { + return id; + } + + public LinkedHashMap getColumnMap() { + return columnMap; + } + + void setTableCheckedStatus(boolean tableCheckedStatus) { + this.tableCheckedStatus = tableCheckedStatus; + } + + @Override + public String toString() { + return name; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableUtils.java b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c8552228bfef82664680817e217eedf4c3541a5e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/db/table/TableUtils.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.db.table; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.db.annotation.Column; +import com.wordplat.quickstart.xutils.db.converter.ColumnConverterFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedHashMap; + +/** + * TableUtils + * + * @since 2021-05-09 + */ +final class TableUtils { + private TableUtils() { + } + + static LinkedHashMap findColumnMap(Class entityType) { + LinkedHashMap columnMap = new LinkedHashMap(); + addColumns2Map(entityType, columnMap); + return columnMap; + } + + private static void addColumns2Map(Class entityType, HashMap columnMap) { + if (Object.class.equals(entityType)) { + return; + } + + try { + Field[] fields = entityType.getDeclaredFields(); + for (Field field : fields) { + int modify = field.getModifiers(); + if (Modifier.isStatic(modify) || Modifier.isTransient(modify)) { + continue; + } + Column columnAnn = field.getAnnotation(Column.class); + if (columnAnn != null) { + if (ColumnConverterFactory.isSupportColumnConverter(field.getType())) { + ColumnEntity column = new ColumnEntity(entityType, field, columnAnn); + if (!columnMap.containsKey(column.getName())) { + columnMap.put(column.getName(), column); + } + } + } + } + + addColumns2Map(entityType.getSuperclass(), columnMap); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ex/BaseException.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/BaseException.java new file mode 100644 index 0000000000000000000000000000000000000000..4ec8efef2952f8c8ca68c8134563a1ea0c047343 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/BaseException.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.ex; + +import java.io.IOException; + +/** + * Author: wyouflf + * Date: 13-7-24 + * Time: 下午3:00 + * + * @since 2021-05-09 + */ +public class BaseException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * BaseException + */ + public BaseException() { + super(); + } + + /** + * BaseException + * + * @param detailMessage + */ + public BaseException(String detailMessage) { + super(detailMessage); + } + + /** + * BaseException + * + * @param detailMessage + * @param throwable + */ + public BaseException(String detailMessage, Throwable throwable) { + super(detailMessage); + this.initCause(throwable); + } + + /** + * BaseException + * + * @param throwable + */ + public BaseException(Throwable throwable) { + super(throwable.getMessage()); + this.initCause(throwable); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ex/DbException.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/DbException.java new file mode 100644 index 0000000000000000000000000000000000000000..08cb61dbe2b2dd6e9af95dec197117bb5fc23b86 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/DbException.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.ex; + +/** + * DbException + * + * @since 2021-05-09 + */ +public class DbException extends BaseException { + private static final long serialVersionUID = 1L; + + /** + * DbException + */ + public DbException() { + } + + /** + * DbException + * + * @param detailMessage + */ + public DbException(String detailMessage) { + super(detailMessage); + } + + /** + * DbException + * + * @param detailMessage + * @param throwable + */ + public DbException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + /** + * DbException + * + * @param throwable + */ + public DbException(Throwable throwable) { + super(throwable); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ex/FileLockedException.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/FileLockedException.java new file mode 100644 index 0000000000000000000000000000000000000000..07cf5b794a89e516e133a64fb84abd37763acd9b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/FileLockedException.java @@ -0,0 +1,19 @@ +package com.wordplat.quickstart.xutils.ex; + +/** + * Created by wyouflf on 15/10/9. + * + * @since 2021-05-09 + */ +public class FileLockedException extends BaseException { + private static final long serialVersionUID = 1L; + + /** + * FileLockedException + * + * @param detailMessage + */ + public FileLockedException(String detailMessage) { + super(detailMessage); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpException.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpException.java new file mode 100644 index 0000000000000000000000000000000000000000..3d71c6c5b7c661df6d88e6882a8e705e095cd631 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpException.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.ex; + +/** + * HttpException + * + * @since 2021-05-09 + */ +public class HttpException extends BaseException { + private static final long serialVersionUID = 1L; + + private int code; + private String errorCode; + private String customMessage; + private String result; + + /** + * HttpException + * + * @param code The http response status code, 0 if the http request error and has no response. + * @param detailMessage The http response message. + */ + public HttpException(int code, String detailMessage) { + super(detailMessage); + this.code = code; + } + + public void setCode(int code) { + this.code = code; + } + + public void setMessage(String message) { + this.customMessage = message; + } + + public int getCode() { + return code; + } + + public String getErrorCode() { + return errorCode == null ? String.valueOf(code) : errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + @Override + public String getMessage() { + if (customMessage != null && customMessage.length() > 0) { + return customMessage; + } else { + return super.getMessage(); + } + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + @Override + public String toString() { + return "errorCode: " + getErrorCode() + ", msg: " + getMessage() + ", result: " + result; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpRedirectException.java b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpRedirectException.java new file mode 100644 index 0000000000000000000000000000000000000000..9d0ab1b77965183efb9b32c79a7b03adb769f619 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/ex/HttpRedirectException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.ex; + +/** + * HttpRedirectException + * + * @since 2021-05-09 + */ +public class HttpRedirectException extends HttpException { + private static final long serialVersionUID = 1L; + + /** + * HttpRedirectException + * + * @param code + * @param detailMessage + * @param result + */ + public HttpRedirectException(int code, String detailMessage, String result) { + super(code, detailMessage); + this.setResult(result); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/BaseParams.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/BaseParams.java new file mode 100644 index 0000000000000000000000000000000000000000..bd5a138bbdcd5680d9a85e8e0e226c3becdaaf21 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/BaseParams.java @@ -0,0 +1,606 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.json.JSONArray; +import com.wordplat.quickstart.json.JSONException; +import com.wordplat.quickstart.json.JSONObject; +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.body.*; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.util.*; + +/** + * 请求的基础参数 + * Created by wyouflf on 16/1/23. + * + * @since 2021-05-09 + */ +public abstract class BaseParams { + private String charset = "UTF-8"; + private HttpMethod method; + private String bodyContent; + private String bodyContentType; + private boolean multipart = false; // 是否使用multipart表单 + private boolean asJsonContent = false; // 用json形式的bodyParams上传 + private boolean asJsonArrayContent = false; // 用json array形式的bodyParams上传 + private RequestBody requestBody; // 生成的表单 + + private final List
headers = new ArrayList
(); + private final List queryStringParams = new ArrayList(); + private final List bodyParams = new ArrayList(); + + /** + * setCharset + * + * @param charset + */ + public void setCharset(String charset) { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + + public String getCharset() { + return charset; + } + + public void setMethod(HttpMethod method) { + this.method = method; + } + + public HttpMethod getMethod() { + return method; + } + + public boolean isMultipart() { + return multipart; + } + + public void setMultipart(boolean multipart) { + this.multipart = multipart; + } + + + public boolean isAsJsonContent() { + return asJsonContent; + } + + public void setAsJsonContent(boolean asJsonContent) { + this.asJsonContent = asJsonContent; + } + + + public boolean isAsJsonArrayContent() { + return asJsonArrayContent; + } + + + public void setAsJsonArrayContent(boolean asJsonArrayContent) { + this.asJsonArrayContent = asJsonArrayContent; + } + + /** + * setHeader + * + * @param name + * @param value + */ + public void setHeader(String name, String value) { + if (TextUtils.isEmpty(name)) { + return; + } + Header header = new Header(name, value, true); + Iterator
it = headers.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + + if (name.equals(kv.key)) { + it.remove(); + } + } + this.headers.add(header); + } + + /** + * addHeader + * + * @param name + * @param value + */ + public void addHeader(String name, String value) { + if (TextUtils.isEmpty(name)) { + return; + } + this.headers.add(new Header(name, value, false)); + } + + /** + * addParameter + * + * @param name + * @param value + */ + public void addParameter(String name, Object value) { + if (HttpMethod.permitsRequestBody(method)) { + addBodyParameter(name, value, null, null); + } else { + addQueryStringParameter(name, value); + } + } + + /** + * addQueryStringParameter + * + * @param name + * @param value + */ + public void addQueryStringParameter(String name, Object value) { + if (TextUtils.isEmpty(name)) { + return; + } + if (value instanceof Iterable) { + for (Object item : (Iterable) value) { + this.queryStringParams.add(new ArrayItem(name, item)); + } + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + int len = array.length(); + for (int i = 0; i < len; i++) { + this.queryStringParams.add(new ArrayItem(name, array.opt(i))); + } + } else if (value != null && value.getClass().isArray()) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + this.queryStringParams.add(new ArrayItem(name, Array.get(value, i))); + } + } else { + this.queryStringParams.add(new KeyValue(name, value)); + } + } + + /** + * addBodyParameter + * + * @param name + * @param value + */ + public void addBodyParameter(String name, Object value) { + addBodyParameter(name, value, null, null); + } + + /** + * addBodyParameter + * + * @param name + * @param value + * @param contentType + */ + public void addBodyParameter(String name, Object value, String contentType) { + addBodyParameter(name, value, contentType, null); + } + + /** + * addBodyParameter + * + * @param name + * @param value + * @param contentType + * @param fileName + */ + public void addBodyParameter(String name, Object value, String contentType, String fileName) { + if (TextUtils.isEmpty(name) && value == null) { + return; + } + if (TextUtils.isEmpty(contentType) && TextUtils.isEmpty(fileName)) { + if (value instanceof Iterable) { + for (Object item : (Iterable) value) { + this.bodyParams.add(new ArrayItem(name, item)); + } + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + int len = array.length(); + for (int i = 0; i < len; i++) { + this.bodyParams.add(new ArrayItem(name, array.opt(i))); + } + } else if (value instanceof byte[]) { + this.bodyParams.add(new KeyValue(name, value)); + } else if (value != null && value.getClass().isArray()) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + this.bodyParams.add(new ArrayItem(name, Array.get(value, i))); + } + } else { + this.bodyParams.add(new KeyValue(name, value)); + } + } else { + this.bodyParams.add(new BodyItemWrapper(name, value, contentType, fileName)); + } + } + + public void setBodyContent(String content) { + this.bodyContent = content; + } + + /** + * getBodyContent + * + * @return String + */ + public String getBodyContent() { + checkBodyParams(); + return bodyContent; + } + + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public List
getHeaders() { + return new ArrayList
(headers); + } + + /** + * getQueryStringParams + * + * @return + */ + public List getQueryStringParams() { + checkBodyParams(); + return new ArrayList(queryStringParams); + } + + /** + * getBodyParams + * + * @return List + */ + public List getBodyParams() { + checkBodyParams(); + return new ArrayList(bodyParams); + } + + /** + * getParams + * + * @param name + * @return List + */ + public List getParams(String name) { + List result = new ArrayList(); + for (KeyValue kv : queryStringParams) { + if (name != null && name.equals(kv.key)) { + result.add(kv); + } + } + for (KeyValue kv : bodyParams) { + if (name == null && kv.key == null) { + result.add(kv); + } else if (name != null && name.equals(kv.key)) { + result.add(kv); + } + } + return result; + } + + /** + * clearParams + */ + public void clearParams() { + queryStringParams.clear(); + bodyParams.clear(); + bodyContent = null; + bodyContentType = null; + requestBody = null; + } + + /** + * removeParameter + * + * @param name + */ + public void removeParameter(String name) { + if (TextUtils.isEmpty(name)) { + bodyContent = null; + bodyContentType = null; + } else { + Iterator it = queryStringParams.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + if (name.equals(kv.key)) { + it.remove(); + } + } + } + + Iterator it = bodyParams.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + if (name == null && kv.key == null) { + it.remove(); + } else if (name != null && name.equals(kv.key)) { + it.remove(); + } + } + } + + public void setRequestBody(RequestBody requestBody) { + this.requestBody = requestBody; + } + + /** + * getRequestBody + * + * @return RequestBody + * @throws IOException + */ + public RequestBody getRequestBody() throws IOException { + checkBodyParams(); + if (this.requestBody != null) { + return this.requestBody; + } + + RequestBody result = null; + if (!TextUtils.isEmpty(bodyContent)) { + result = new StringBody(bodyContent, charset); + result.setContentType(bodyContentType); + } else if (multipart) { + result = new MultipartBody(bodyParams, charset); + result.setContentType(bodyContentType); + } else if (bodyParams.size() == 1) { + KeyValue kv = bodyParams.get(0); + String name = kv.key; + Object value = kv.value; + String contentType = null; + if (kv instanceof BodyItemWrapper) { + BodyItemWrapper wrapper = (BodyItemWrapper) kv; + contentType = wrapper.contentType; + } + if (TextUtils.isEmpty(contentType)) { + contentType = bodyContentType; + } + if (value instanceof File) { + result = new FileBody((File) value, contentType); + } else if (value instanceof InputStream) { + result = new InputStreamBody((InputStream) value, contentType); + } else if (value instanceof byte[]) { + result = new InputStreamBody(new ByteArrayInputStream((byte[]) value), contentType); + } else { + if (TextUtils.isEmpty(name)) { + result = new StringBody(kv.getValueStrOrEmpty(), charset); + result.setContentType(contentType); + } else { + result = new UrlEncodedBody(bodyParams, charset); + result.setContentType(contentType); + } + } + } else { + result = new UrlEncodedBody(bodyParams, charset); + result.setContentType(bodyContentType); + } + + return result; + } + + /** + * toJSONString + * + * @return String + * @throws JSONException + */ + public String toJSONString() throws JSONException { + return toJSONString(true); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + if (!queryStringParams.isEmpty()) { + for (KeyValue kv : queryStringParams) { + sb.append(kv.key).append("=").append(kv.value).append("&"); + } + sb.deleteCharAt(sb.length() - 1); + } + + if (!TextUtils.isEmpty(bodyContent)) { + sb.append("<").append(bodyContent).append(">"); + } else if (!bodyParams.isEmpty()) { + sb.append("<"); + for (KeyValue kv : bodyParams) { + sb.append(kv.key).append("=").append(kv.value).append("&"); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(">"); + } + return sb.toString(); + } + + private synchronized void checkBodyParams() { + if (bodyParams.isEmpty()) { + return; + } + + if (requestBody != null || !HttpMethod.permitsRequestBody(method)) { + queryStringParams.addAll(bodyParams); + bodyParams.clear(); + return; + } + + if (asJsonContent || asJsonArrayContent) { + try { + bodyContent = toJSONString(false); + bodyParams.clear(); + } catch (JSONException ex) { + throw new IllegalArgumentException(ex.getMessage(), ex); + } + } else if (!TextUtils.isEmpty(bodyContent)) { + queryStringParams.addAll(bodyParams); + bodyParams.clear(); + } + } + + private void params2Json(final JSONObject jsonObject, final List paramList) throws JSONException { + HashSet arraySet = new HashSet(paramList.size()); + LinkedHashMap tempData = new LinkedHashMap(paramList.size()); + for (int i = 0; i < paramList.size(); i++) { + KeyValue kv = paramList.get(i); + final String key = kv.key; + if (TextUtils.isEmpty(key)) { + continue; + } + + JSONArray ja = null; + if (tempData.containsKey(key)) { + ja = tempData.get(key); + } else { + ja = new JSONArray(); + tempData.put(key, ja); + } + + ja.put(RequestParamsHelper.parseJSONObject(kv.value)); + + if (kv instanceof ArrayItem) { + arraySet.add(key); + } + } + + for (Map.Entry entry : tempData.entrySet()) { + String key = entry.getKey(); + JSONArray ja = entry.getValue(); + if (ja.length() > 1 || arraySet.contains(key)) { + jsonObject.put(key, ja); + } else { + Object value = ja.get(0); + jsonObject.put(key, value); + } + } + } + + /** + * toJSONString + * + * @param withQueryString + * @return String + * @throws JSONException + */ + private String toJSONString(boolean withQueryString) throws JSONException { + JSONArray jsonArray = null; + JSONObject jsonObject = null; + if (!TextUtils.isEmpty(bodyContent)) { + if (bodyContent.trim().startsWith("[")) { + jsonArray = new JSONArray(bodyContent); + if (jsonArray.length() > 0) { + Object first = jsonArray.get(0); + if (first instanceof JSONObject) { + jsonObject = (JSONObject) first; + } else { + LogUtil.w("only contains bodyContent"); + return jsonArray.toString(); + } + } else { + jsonObject = new JSONObject(); + jsonArray.put(jsonObject); + } + } else { + jsonObject = new JSONObject(bodyContent); + } + } else { + jsonObject = new JSONObject(); + if (asJsonArrayContent) { + jsonArray = new JSONArray(); + jsonArray.put(jsonObject); + } + } + + if (withQueryString) { + List list = new ArrayList(queryStringParams.size() + bodyParams.size()); + list.addAll(queryStringParams); + list.addAll(bodyParams); + params2Json(jsonObject, list); + } else { + params2Json(jsonObject, bodyParams); + } + + return jsonArray != null ? jsonArray.toString() : jsonObject.toString(); + } + + /** + * ArrayItem + * + * @since 2021-05-09 + */ + public static final class ArrayItem extends KeyValue { + /** + * ArrayItem + * + * @param key + * @param value + */ + public ArrayItem(String key, Object value) { + super(key, value); + } + } + + /** + * Header + * + * @since 2021-05-09 + */ + public static final class Header extends KeyValue { + /** + * setHeader + */ + public final boolean setHeader; + + /** + * Header + * + * @param key + * @param value + * @param setHeader + */ + public Header(String key, String value, boolean setHeader) { + super(key, value); + this.setHeader = setHeader; + } + } + + /** + * BodyItemWrapper + * + * @since 2021-05-09 + */ + public static final class BodyItemWrapper extends KeyValue { + /** + * fileName + */ + public final String fileName; + /** + * contentType + */ + public final String contentType; + + /** + * BodyItemWrapper + * + * @param key + * @param value + * @param contentType + * @param fileName + */ + public BodyItemWrapper(String key, Object value, String contentType, String fileName) { + super(key, value); + if (TextUtils.isEmpty(contentType)) { + this.contentType = "application/octet-stream"; + } else { + this.contentType = contentType; + } + this.fileName = fileName; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpManagerImpl.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0e9b28261c8545cab7123c01e064b341213921a9 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpManagerImpl.java @@ -0,0 +1,114 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.xutils.HttpManager; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.x; + +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/7/23. + * HttpManager实现 + * + * @since 2021-05-09 + */ +public final class HttpManagerImpl implements HttpManager { + private static final Object lock = new Object(); + private static volatile HttpManagerImpl instance; + + private HttpManagerImpl() { + } + + /** + * registerInstance + */ + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new HttpManagerImpl(); + } + } + } + x.Ext.setHttpManager(instance); + } + + @Override + public Callback.Cancelable get(RequestParams entity, Callback.CommonCallback callback) { + return request(HttpMethod.GET, entity, callback); + } + + @Override + public Callback.Cancelable post(RequestParams entity, Callback.CommonCallback callback) { + return request(HttpMethod.POST, entity, callback); + } + + @Override + public Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback callback) { + entity.setMethod(method); + Callback.Cancelable cancelable = null; + if (callback instanceof Callback.Cancelable) { + cancelable = (Callback.Cancelable) callback; + } + HttpTask task = new HttpTask(entity, cancelable, callback); + return x.task().start(task); + } + + @Override + public T getSync(RequestParams entity, Class resultType) throws Throwable { + return requestSync(HttpMethod.GET, entity, resultType); + } + + @Override + public T postSync(RequestParams entity, Class resultType) throws Throwable { + return requestSync(HttpMethod.POST, entity, resultType); + } + + @Override + public T requestSync(HttpMethod method, RequestParams entity, Class resultType) throws Throwable { + DefaultSyncCallback callback = new DefaultSyncCallback(resultType); + return requestSync(method, entity, callback); + } + + @Override + public T requestSync(HttpMethod method, RequestParams entity, Callback.TypedCallback callback) throws Throwable { + entity.setMethod(method); + HttpTask task = new HttpTask(entity, null, callback); + return x.task().startSync(task); + } + + /** + * DefaultSyncCallback + * + * @param + */ + private class DefaultSyncCallback implements Callback.TypedCallback { + + private final Class resultType; + + public DefaultSyncCallback(Class resultType) { + this.resultType = resultType; + } + + @Override + public Type getLoadType() { + return resultType; + } + + @Override + public void onSuccess(T result) { + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + } + + @Override + public void onCancelled(CancelledException cex) { + } + + @Override + public void onFinished() { + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpMethod.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..2660eb2be66c2db0bf3224f2ac19b9163edbf063 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpMethod.java @@ -0,0 +1,70 @@ +package com.wordplat.quickstart.xutils.http; + +/** + * Created by wyouflf on 15/8/4. + * HTTP谓词枚举 + * + * @since 2021-05-09 + */ +public enum HttpMethod { + /** + * GET("GET"), + * POST("POST"), + * PUT("PUT"), + * PATCH("PATCH"), + * HEAD("HEAD"), + * MOVE("MOVE"), + * COPY("COPY"), + * DELETE("DELETE"), + * OPTIONS("OPTIONS"), + * TRACE("TRACE"), + * CONNECT("CONNECT"); + */ + GET("GET"), POST("POST"), PUT("PUT"), PATCH("PATCH"), HEAD("HEAD"), MOVE("MOVE"), + COPY("COPY"), DELETE("DELETE"), OPTIONS("OPTIONS"), TRACE("TRACE"), CONNECT("CONNECT"); + + private final String value; + + HttpMethod(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + /** + * permitsRetry + * + * @param method + * @return boolean + */ + public static boolean permitsRetry(HttpMethod method) { + return method == GET; + } + + /** + * permitsCache + * + * @param method + * @return boolean + */ + public static boolean permitsCache(HttpMethod method) { + return method == GET || method == POST; + } + + /** + * permitsRequestBody + * + * @param method + * @return boolean + */ + public static boolean permitsRequestBody(HttpMethod method) { + return method == null + || method == POST + || method == PUT + || method == PATCH + || method == DELETE; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpTask.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpTask.java new file mode 100644 index 0000000000000000000000000000000000000000..e42df7cb35e34aa5dd1ac6df310b0715a213d078 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/HttpTask.java @@ -0,0 +1,642 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.task.AbsTask; +import com.wordplat.quickstart.xutils.common.task.Priority; +import com.wordplat.quickstart.xutils.common.task.PriorityExecutor; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.ParameterizedTypeUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.ex.HttpException; +import com.wordplat.quickstart.xutils.ex.HttpRedirectException; +import com.wordplat.quickstart.xutils.http.app.HttpRetryHandler; +import com.wordplat.quickstart.xutils.http.app.RedirectHandler; +import com.wordplat.quickstart.xutils.http.app.RequestInterceptListener; +import com.wordplat.quickstart.xutils.http.app.RequestTracker; +import com.wordplat.quickstart.xutils.http.request.UriRequest; +import com.wordplat.quickstart.xutils.http.request.UriRequestFactory; +import com.wordplat.quickstart.xutils.x; + +import java.io.Closeable; +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/7/23. + * http 请求任务 + * + * @since 2021-05-09 + */ +public class HttpTask extends AbsTask implements ProgressHandler { + private static final int FLAG_REQUEST_CREATED = 1; + private static final int FLAG_CACHE = 2; + private static final int FLAG_PROGRESS = 3; + + private static final PriorityExecutor HTTP_EXECUTOR = new PriorityExecutor(5, true); + private static final PriorityExecutor CACHE_EXECUTOR = new PriorityExecutor(5, true); + /** + * 文件下载任务 + */ + private static final AtomicInteger sCurrFileLoadCount = new AtomicInteger(0); + private static final HashMap>> + DOWNLOAD_TASK = new HashMap>>(1); + + /** + * 线程池 + */ + private final Executor executor; + private long lastUpdateTime; + private long loadingUpdateMaxTimeSpan = 300L; + /** + * 请求相关 + */ + private RequestParams params; + private UriRequest request; + private Type loadType; + private volatile boolean hasException = false; + private final Callback.CommonCallback callback; + + /** + * 缓存控制 + */ + private Object rawResult = null; + private volatile Boolean trustCache = null; + private final Object cacheLock = new Object(); + + /** + * 扩展callback + */ + private Callback.CacheCallback cacheCallback; + private Callback.PrepareCallback prepareCallback; + private Callback.ProgressCallback progressCallback; + private RequestInterceptListener requestInterceptListener; + + /** + * 日志追踪 + */ + private RequestTracker tracker; + + /** + * HttpTask + * + * @param params + * @param cancelHandler + * @param callback + */ + public HttpTask(RequestParams params, Callback.Cancelable cancelHandler, + Callback.CommonCallback callback) { + super(cancelHandler); + + assert params != null; + assert callback != null; + + this.params = params; + this.callback = callback; + + if (callback instanceof Callback.CacheCallback) { + this.cacheCallback = (Callback.CacheCallback) callback; + } + if (callback instanceof Callback.PrepareCallback) { + this.prepareCallback = (Callback.PrepareCallback) callback; + } + if (callback instanceof Callback.ProgressCallback) { + this.progressCallback = (Callback.ProgressCallback) callback; + } + if (callback instanceof RequestInterceptListener) { + this.requestInterceptListener = (RequestInterceptListener) callback; + } + + // init tracker + { + RequestTracker customTracker = params.getRequestTracker(); + if (customTracker == null) { + if (callback instanceof RequestTracker) { + customTracker = (RequestTracker) callback; + } else { + customTracker = UriRequestFactory.getDefaultTracker(); + } + } + if (customTracker != null) { + tracker = new RequestTrackerWrapper(customTracker); + } + } + + // init executor + if (params.getExecutor() != null) { + this.executor = params.getExecutor(); + } else { + if (cacheCallback != null) { + this.executor = CACHE_EXECUTOR; + } else { + this.executor = HTTP_EXECUTOR; + } + } + } + + // 解析loadType + private void resolveLoadType() { + Class callBackType = callback.getClass(); + if (callback instanceof Callback.TypedCallback) { + loadType = ((Callback.TypedCallback) callback).getLoadType(); + } else if (callback instanceof Callback.PrepareCallback) { + loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.PrepareCallback.class, 0); + } else { + loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.CommonCallback.class, 0); + } + } + + // 初始化请求参数 + private UriRequest createNewRequest() throws Throwable { + // init request + params.init(); + UriRequest result = UriRequestFactory.getUriRequest(params, loadType); + result.setProgressHandler(this); + this.loadingUpdateMaxTimeSpan = params.getLoadingUpdateMaxTimeSpan(); + this.update(FLAG_REQUEST_CREATED, result); + return result; + } + + // 文件下载冲突检测 + private void checkDownloadTask() { + if (File.class == loadType) { + synchronized (DOWNLOAD_TASK) { + String downloadTaskKey = this.params.getSaveFilePath(); + /*{ + // 不处理缓存文件下载冲突, + // 缓存文件下载冲突会抛出FileLockedException异常, + // 回调方法中处理控制是否重新尝试下载. + }*/ + if (!TextUtils.isEmpty(downloadTaskKey)) { + WeakReference> taskRef = DOWNLOAD_TASK.get(downloadTaskKey); + if (taskRef != null) { + HttpTask task = taskRef.get(); + if (task != null) { + task.cancel(); + task.closeRequestSync(); + } + DOWNLOAD_TASK.remove(downloadTaskKey); + } + DOWNLOAD_TASK.put(downloadTaskKey, new WeakReference>(this)); + } // end if (!TextUtils.isEmpty(downloadTaskKey)) + + if (DOWNLOAD_TASK.size() > RequestParams.MAX_FILE_LOAD_WORKER) { + Iterator>>> + entryItr = DOWNLOAD_TASK.entrySet().iterator(); + while (entryItr.hasNext()) { + Map.Entry>> next = entryItr.next(); + WeakReference> value = next.getValue(); + if (value == null || value.get() == null) { + entryItr.remove(); + } + } + } + } // end synchronized + } + } + + @Override + protected ResultType doBackground() throws Throwable { + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + ResultType result = null; + resolveLoadType(); + request = createNewRequest(); + checkDownloadTask(); + boolean retry = true; + int retryCount = 0; + Throwable exception = null; + HttpRetryHandler retryHandler = this.params.getHttpRetryHandler(); + if (retryHandler == null) { + retryHandler = new HttpRetryHandler(); + } + retryHandler.setMaxRetryCount(this.params.getMaxRetryCount()); + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + // 检查缓存 + Object cacheResult = null; + if (cacheCallback != null && HttpMethod.permitsCache(params.getMethod())) { + // 尝试从缓存获取结果, 并为请求头加入缓存控制参数. + try { + clearRawResult(); + LogUtil.d("load cache: " + this.request.getRequestUri()); + rawResult = this.request.loadResultFromCache(); + } catch (Throwable ex) { + LogUtil.w("load disk cache error", ex); + } + + if (this.isCancelled()) { + clearRawResult(); + throw new Callback.CancelledException("cancelled before request"); + } + + if (rawResult != null) { + if (prepareCallback != null) { + try { + cacheResult = prepareCallback.prepare(rawResult); + } catch (Throwable ex) { + cacheResult = null; + LogUtil.w("prepare disk cache error", ex); + } finally { + clearRawResult(); + } + } else { + cacheResult = rawResult; + } + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + if (cacheResult != null) { + // 同步等待是否信任缓存 + this.update(FLAG_CACHE, cacheResult); + synchronized (cacheLock) { + while (trustCache == null) { + try { + cacheLock.wait(); + } catch (InterruptedException iex) { + throw new Callback.CancelledException("cancelled before request"); + } catch (Throwable ignored) { + String ss = ignored.toString(); + } + } + } + if (trustCache) { + return null; + } + } + } + } + + if (trustCache == null) { + trustCache = false; + } + + if (cacheResult == null) { + this.request.clearCacheHeader(); + } + + // 判断请求的缓存策略 + if (callback instanceof Callback.ProxyCacheCallback) { + if (((Callback.ProxyCacheCallback) callback).onlyCache()) { + return null; + } + } + + // 发起请求 + retry = true; + while (retry) { + retry = false; + + try { + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + this.request.close(); + + try { + clearRawResult(); + LogUtil.d("load: " + this.request.getRequestUri()); + RequestWorker requestWorker = new RequestWorker(); + requestWorker.request(); + if (requestWorker.ex != null) { + throw requestWorker.ex; + } + rawResult = requestWorker.result; + } catch (Throwable ex) { + clearRawResult(); + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled during request"); + } else { + throw ex; + } + } + + if (prepareCallback != null) { + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + try { + result = (ResultType) prepareCallback.prepare(rawResult); + } finally { + clearRawResult(); + } + } else { + result = (ResultType) rawResult; + } + + // 保存缓存 + if (cacheCallback != null && HttpMethod.permitsCache(params.getMethod())) { + try { + this.request.save2Cache(); + } catch (Throwable ex) { + LogUtil.e("Error while storing the http cache.", ex); + } + } + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled after request"); + } + } catch (HttpRedirectException redirectEx) { + retry = true; + LogUtil.w("Http Redirect:" + params.getUri()); + } catch (Throwable ex) { + switch (this.request.getResponseCode()) { + case 204: // empty content + case 205: // empty content + case 304: // disk cache is valid. + return null; + default: { + exception = ex; + if (this.isCancelled() && !(exception instanceof Callback.CancelledException)) { + exception = new Callback.CancelledException("canceled by user"); + } + retry = retryHandler.canRetry(this.request, exception, ++retryCount); + } + } + } + } + + if (exception != null && result == null && !trustCache) { + hasException = true; + throw exception; + } + return result; + } + + @Override + @SuppressWarnings("unchecked") + protected void onUpdate(int flag, Object... args) { + switch (flag) { + case FLAG_REQUEST_CREATED: { + if (this.tracker != null) { + this.tracker.onRequestCreated((UriRequest) args[0]); + } + break; + } + case FLAG_CACHE: { + synchronized (cacheLock) { + try { + ResultType result = (ResultType) args[0]; + if (tracker != null) { + tracker.onCache(request, result); + } + trustCache = this.cacheCallback.onCache(result); + } catch (Throwable ex) { + trustCache = false; + callback.onError(ex, true); + } finally { + cacheLock.notifyAll(); + } + } + break; + } + case FLAG_PROGRESS: { + if (this.progressCallback != null && args.length == 3) { + try { + this.progressCallback.onLoading( + ((Number) args[0]).longValue(), + ((Number) args[1]).longValue(), + (Boolean) args[2]); + } catch (Throwable ex) { + callback.onError(ex, true); + } + } + break; + } + default: { + break; + } + } + } + + @Override + protected void onWaiting() { + if (tracker != null) { + tracker.onWaiting(params); + } + if (progressCallback != null) { + progressCallback.onWaiting(); + } + } + + @Override + protected void onStarted() { + if (tracker != null) { + tracker.onStart(params); + } + if (progressCallback != null) { + progressCallback.onStarted(); + } + } + + @Override + protected void onSuccess(ResultType result) { + if (hasException) { + return; + } + if (tracker != null) { + tracker.onSuccess(request, result); + } + callback.onSuccess(result); + } + + @Override + protected void onError(Throwable ex, boolean isCallbackError) { + if (tracker != null) { + tracker.onError(request, ex, isCallbackError); + } + callback.onError(ex, isCallbackError); + } + + @Override + protected void onCancelled(Callback.CancelledException cex) { + if (tracker != null) { + tracker.onCancelled(request); + } + callback.onCancelled(cex); + } + + @Override + protected void onFinished() { + if (tracker != null) { + tracker.onFinished(request); + } + x.task().run(new Runnable() { + @Override + public void run() { + closeRequestSync(); + } + }); + callback.onFinished(); + } + + private void clearRawResult() { + if (rawResult instanceof Closeable) { + IOUtil.closeQuietly((Closeable) rawResult); + } + rawResult = null; + } + + @Override + protected void cancelWorks() { + x.task().run(new Runnable() { + @Override + public void run() { + closeRequestSync(); + } + }); + } + + @Override + protected boolean isCancelFast() { + return params.isCancelFast(); + } + + private void closeRequestSync() { + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + sCurrFileLoadCount.notifyAll(); + } + } + clearRawResult(); + IOUtil.closeQuietly(request); + } + + @Override + public Executor getExecutor() { + return this.executor; + } + + @Override + public Priority getPriority() { + return params.getPriority(); + } + + @Override + public boolean updateProgress(long total, long current, boolean forceUpdateUI) { + if (isCancelled() || isFinished()) { + return false; + } + + if (progressCallback != null && request != null && current > 0) { + if (total < 0) { + total = -1; + } else if (total < current) { + total = current; + } + if (forceUpdateUI) { + lastUpdateTime = System.currentTimeMillis(); + this.update(FLAG_PROGRESS, total, current, request.isLoading()); + } else { + long currTime = System.currentTimeMillis(); + if (currTime - lastUpdateTime >= loadingUpdateMaxTimeSpan) { + lastUpdateTime = currTime; + this.update(FLAG_PROGRESS, total, current, request.isLoading()); + } + } + } + + return !isCancelled() && !isFinished(); + } + + @Override + public String toString() { + return params.toString(); + } + + /** + * 请求发送和加载数据线程. + * 该线程被join到HttpTask的工作线程去执行. + * 它的主要作用是为了能强行中断请求的链接过程; + * 并辅助限制同时下载文件的线程数. + */ + private final class RequestWorker { + Object result; + Throwable ex; + + private RequestWorker() { + } + + public void request() { + try { + boolean interrupted = false; + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + while (sCurrFileLoadCount.get() >= RequestParams.MAX_FILE_LOAD_WORKER + && !HttpTask.this.isCancelled()) { + try { + sCurrFileLoadCount.wait(10); + } catch (InterruptedException iex) { + interrupted = true; + break; + } catch (Throwable ignored) { + } + } + } + sCurrFileLoadCount.incrementAndGet(); + } + + if (interrupted || HttpTask.this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request" + + (interrupted ? "(interrupted)" : "")); + } + + try { + request.setRequestInterceptListener(requestInterceptListener); + this.result = request.loadResult(); + } catch (Throwable ex) { + this.ex = ex; + } + + if (this.ex != null) { + throw this.ex; + } + } catch (Throwable ex) { + this.ex = ex; + if (ex instanceof HttpException) { + HttpException httpEx = (HttpException) ex; + int errorCode = httpEx.getCode(); + if (errorCode == 301 || errorCode == 302) { + RedirectHandler redirectHandler = params.getRedirectHandler(); + if (redirectHandler != null) { + try { + RequestParams redirectParams = redirectHandler.getRedirectParams(request); + if (redirectParams != null) { + if (redirectParams.getMethod() == null) { + redirectParams.setMethod(params.getMethod()); + } + HttpTask.this.params = redirectParams; + HttpTask.this.request = createNewRequest(); + this.ex = new HttpRedirectException(errorCode, + httpEx.getMessage(), httpEx.getResult()); + } + } catch (Throwable throwable) { + this.ex = ex; + } + } + } + } + } finally { + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + sCurrFileLoadCount.decrementAndGet(); + sCurrFileLoadCount.notifyAll(); + } + } + } + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/ProgressHandler.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/ProgressHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..62e3e6c7e2b5a6bc6513b514554d1320a90e2e5f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/ProgressHandler.java @@ -0,0 +1,21 @@ +package com.wordplat.quickstart.xutils.http; + +/** + * 进度控制接口, updateProgress方式中ProgressCallback#onLoading. + * 默认最长间隔300毫秒调用一次. + * Author: wyouflf + * Time: 2014/05/23 + * + * @since 2021-05-09 + */ +public interface ProgressHandler { + /** + * updateProgress + * + * @param total + * @param current + * @param forceUpdateUI + * @return boolean + */ + boolean updateProgress(long total, long current, boolean forceUpdateUI); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParams.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParams.java new file mode 100644 index 0000000000000000000000000000000000000000..3f288169d398a5aa76562f203a9b4c5caa432196 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParams.java @@ -0,0 +1,380 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.xutils.common.task.Priority; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.annotation.HttpRequest; +import com.wordplat.quickstart.xutils.http.app.*; +import com.wordplat.quickstart.xutils.x; + +import ohos.app.Context; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.net.Proxy; +import java.util.concurrent.Executor; + +/** + * Created by wyouflf on 15/7/17. + * 网络请求参数实体 + * + * @since 2021-05-09 + */ +public class RequestParams extends BaseParams { + /** + * int MAX_FILE_LOAD_WORKER + */ + public static final int MAX_FILE_LOAD_WORKER = 10; + private static final DefaultRedirectHandler DEFAULT_REDIRECT_HANDLER = new DefaultRedirectHandler(); + + // 注解及其扩展参数 + private HttpRequest httpRequest; + private String uri; + private final String[] signs; + private final String[] cacheKeys; + private ParamsBuilder builder; + private String buildUri; + private String buildCacheKey; + private SSLSocketFactory sslSocketFactory; + + private boolean invokedGetHttpRequest = false; + + // 扩展参数 + private Context context; + private Proxy proxy; // 代理 + private HostnameVerifier hostnameVerifier; // https域名校验 + private boolean useCookie = true; // 是否在请求过程中启用cookie + private String cacheDirName; // 缓存文件夹名称 + private long cacheSize; // 缓存文件夹大小 + private long cacheMaxAge; // 默认缓存存活时间, 单位:毫秒.(如果服务没有返回有效的max-age或Expires) + private Executor executor; // 自定义线程池 + private Priority priority = Priority.DEFAULT; // 请求优先级 + private int connectTimeout = 1000 * 15; // 连接超时时间 + private int readTimeout = 1000 * 15; // 读取超时时间 + private boolean autoResume = true; // 是否在下载是自动断点续传 + private boolean autoRename = false; // 是否根据头信息自动命名文件 + private int maxRetryCount = 2; // 最大请求错误重试次数 + private String saveFilePath; // 下载文件时文件保存的路径和文件名 + private boolean cancelFast = false; // 是否可以被立即停止, true: 为请求创建新的线程, 取消时请求线程被立即中断. + private int loadingUpdateMaxTimeSpan = 300; // 进度刷新最大间隔时间(ms) + private HttpRetryHandler httpRetryHandler; // 自定义HttpRetryHandler + private RequestTracker requestTracker; // 自定义日志记录接口. + private RedirectHandler redirectHandler = DEFAULT_REDIRECT_HANDLER; + + /** + * RequestParams + */ + public RequestParams() { + this(null, null, null, null); + } + + /** + * RequestParams + * + * @param uri + */ + public RequestParams(String uri) { + this(uri, null, null, null); + } + + /** + * RequestParams + * + * @param uri + * @param builder + * @param signs + * @param cacheKeys + */ + public RequestParams(String uri, ParamsBuilder builder, String[] signs, String[] cacheKeys) { + if (uri != null && builder == null) { + builder = new DefaultParamsBuilder(); + } + this.uri = uri; + this.signs = signs; + this.cacheKeys = cacheKeys; + this.builder = builder; + this.context = x.app(); + } + + // invoke via HttpTask#createNewRequest + void init() throws Throwable { + if (!TextUtils.isEmpty(buildUri)) { + return; + } + + if (TextUtils.isEmpty(uri) && getHttpRequest() == null) { + throw new IllegalStateException("uri is empty && @HttpRequest == null"); + } + + // init params from entity + initEntityParams(); + + // build uri & cacheKey + buildUri = uri; + HttpRequest request = this.getHttpRequest(); + if (request != null) { + builder = request.builder().newInstance(); + buildUri = builder.buildUri(this, request); + builder.buildParams(this); + builder.buildSign(this, request.signs()); + if (sslSocketFactory == null) { + sslSocketFactory = builder.getSSLSocketFactory(); + } + } else if (this.builder != null) { + builder.buildParams(this); + builder.buildSign(this, signs); + if (sslSocketFactory == null) { + sslSocketFactory = builder.getSSLSocketFactory(); + } + } + } + + /** + * getUri + * + * @return String + */ + public String getUri() { + return TextUtils.isEmpty(buildUri) ? uri : buildUri; + } + + /** + * setUri + * + * @param uri + */ + public void setUri(String uri) { + if (TextUtils.isEmpty(buildUri)) { + this.uri = uri; + } else { + this.buildUri = uri; + } + } + + /** + * getCacheKey + * + * @return String + */ + public String getCacheKey() { + if (TextUtils.isEmpty(buildCacheKey) && builder != null) { + HttpRequest request = this.getHttpRequest(); + if (request != null) { + buildCacheKey = builder.buildCacheKey(this, request.cacheKeys()); + } else { + buildCacheKey = builder.buildCacheKey(this, cacheKeys); + } + } + return buildCacheKey; + } + + public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + public boolean isUseCookie() { + return useCookie; + } + + public void setUseCookie(boolean useCookie) { + this.useCookie = useCookie; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + + public Proxy getProxy() { + return proxy; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public Priority getPriority() { + return priority; + } + + public void setPriority(Priority priority) { + this.priority = priority; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + /** + * setConnectTimeout + * + * @param connectTimeout + */ + public void setConnectTimeout(int connectTimeout) { + if (connectTimeout > 0) { + this.connectTimeout = connectTimeout; + } + } + + public int getReadTimeout() { + return readTimeout; + } + + /** + * setReadTimeout + * + * @param readTimeout + */ + public void setReadTimeout(int readTimeout) { + if (readTimeout > 0) { + this.readTimeout = readTimeout; + } + } + + public String getCacheDirName() { + return cacheDirName; + } + + public void setCacheDirName(String cacheDirName) { + this.cacheDirName = cacheDirName; + } + + public long getCacheSize() { + return cacheSize; + } + + public void setCacheSize(long cacheSize) { + this.cacheSize = cacheSize; + } + + public long getCacheMaxAge() { + return cacheMaxAge; + } + + public void setCacheMaxAge(long cacheMaxAge) { + this.cacheMaxAge = cacheMaxAge; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public boolean isAutoResume() { + return autoResume; + } + + public void setAutoResume(boolean autoResume) { + this.autoResume = autoResume; + } + + public boolean isAutoRename() { + return autoRename; + } + + public void setAutoRename(boolean autoRename) { + this.autoRename = autoRename; + } + + public String getSaveFilePath() { + return saveFilePath; + } + + public void setSaveFilePath(String saveFilePath) { + this.saveFilePath = saveFilePath; + } + + public int getMaxRetryCount() { + return maxRetryCount; + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public boolean isCancelFast() { + return cancelFast; + } + + public void setCancelFast(boolean cancelFast) { + this.cancelFast = cancelFast; + } + + public int getLoadingUpdateMaxTimeSpan() { + return loadingUpdateMaxTimeSpan; + } + + public void setLoadingUpdateMaxTimeSpan(int loadingUpdateMaxTimeSpan) { + this.loadingUpdateMaxTimeSpan = loadingUpdateMaxTimeSpan; + } + + public HttpRetryHandler getHttpRetryHandler() { + return httpRetryHandler; + } + + public void setHttpRetryHandler(HttpRetryHandler httpRetryHandler) { + this.httpRetryHandler = httpRetryHandler; + } + + public RedirectHandler getRedirectHandler() { + return redirectHandler; + } + + public void setRedirectHandler(RedirectHandler redirectHandler) { + this.redirectHandler = redirectHandler; + } + + public RequestTracker getRequestTracker() { + return requestTracker; + } + + public void setRequestTracker(RequestTracker requestTracker) { + this.requestTracker = requestTracker; + } + + private void initEntityParams() { + RequestParamsHelper.parseKV(this, this.getClass(), new RequestParamsHelper.ParseKVListener() { + @Override + public void onParseKV(String name, Object value) { + addParameter(name, value); + } + }); + } + + private HttpRequest getHttpRequest() { + if (httpRequest == null && !invokedGetHttpRequest) { + invokedGetHttpRequest = true; + Class thisCls = this.getClass(); + if (thisCls != RequestParams.class) { + httpRequest = thisCls.getAnnotation(HttpRequest.class); + } + } + + return httpRequest; + } + + @Override + public String toString() { + String url = this.getUri(); + String baseParamsStr = super.toString(); + return TextUtils.isEmpty(url) + ? baseParamsStr : url + (url.contains("?") ? "&" : "?") + baseParamsStr; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParamsHelper.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParamsHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..2141e3a6b397cf7a1abe9e6931dff499b9f4e13d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestParamsHelper.java @@ -0,0 +1,113 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.json.JSONArray; +import com.wordplat.quickstart.json.JSONException; +import com.wordplat.quickstart.json.JSONObject; +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +import ohos.utils.Sequenceable; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +/** + * Created by wyouflf on 16/1/23. + */ +final class RequestParamsHelper { + private static final ClassLoader BOOT_CL = String.class.getClassLoader(); + + private RequestParamsHelper() { + } + + interface ParseKVListener { + void onParseKV(String name, Object value); + } + + static void parseKV(Object entity, Class type, ParseKVListener listener) { + if (entity == null || type == null || type == RequestParams.class || type == Object.class) { + return; + } else { + ClassLoader cl = type.getClassLoader(); + if (cl == null || cl == BOOT_CL) { + return; + } + } + + Field[] fields = type.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + String name = field.getName(); + if (!Modifier.isTransient(field.getModifiers()) + && !"serialVersionUID".equals(name) + && field.getType() != Sequenceable.Producer.class) { + try { + field.setAccessible(true); + Object value = field.get(entity); + if (value != null) { + listener.onParseKV(name, value); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + parseKV(entity, type.getSuperclass(), listener); + } + + static Object parseJSONObject(Object value) throws JSONException { + if (value == null) { + return null; + } + + Object result = value; + Class cls = value.getClass(); + if (cls.isArray()) { + JSONArray array = new JSONArray(); + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + array.put(parseJSONObject(Array.get(value, i))); + } + result = array; + } else if (value instanceof Iterable) { + JSONArray array = new JSONArray(); + Iterable list = (Iterable) value; + for (Object item : list) { + array.put(parseJSONObject(item)); + } + result = array; + } else if (value instanceof Map) { + final JSONObject jo = new JSONObject(); + Map map = (Map) value; + for (Map.Entry entry : map.entrySet()) { + Object k = entry.getKey(); + Object v = entry.getValue(); + if (k != null && v != null) { + jo.put(String.valueOf(k), parseJSONObject(v)); + } + } + result = jo; + } else { + ClassLoader cl = cls.getClassLoader(); + if (cl != null && cl != BOOT_CL) { + final JSONObject jo = new JSONObject(); + parseKV(value, cls, new ParseKVListener() { + @Override + public void onParseKV(String name, Object value) { + try { + value = parseJSONObject(value); + jo.put(name, value); + } catch (JSONException ex) { + throw new IllegalArgumentException("parse RequestParams to json failed", ex); + } + } + }); + result = jo; + } + } + return result; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestTrackerWrapper.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestTrackerWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..09696049dc84aebd83e33fd601d7a14d1a986a0e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/RequestTrackerWrapper.java @@ -0,0 +1,90 @@ +package com.wordplat.quickstart.xutils.http; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.http.app.RequestTracker; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/4. + * Wrapper for tracker + */ +final class RequestTrackerWrapper implements RequestTracker { + + private final RequestTracker base; + + public RequestTrackerWrapper(RequestTracker base) { + this.base = base; + } + + @Override + public void onWaiting(RequestParams params) { + try { + base.onWaiting(params); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onStart(RequestParams params) { + try { + base.onStart(params); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onRequestCreated(UriRequest request) { + try { + base.onRequestCreated(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onCache(UriRequest request, Object result) { + try { + base.onCache(request, result); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onSuccess(UriRequest request, Object result) { + try { + base.onSuccess(request, result); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onCancelled(UriRequest request) { + try { + base.onCancelled(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onError(UriRequest request, Throwable ex, boolean isCallbackError) { + try { + base.onError(request, ex, isCallbackError); + } catch (Throwable exOnError) { + LogUtil.e(exOnError.getMessage(), exOnError); + } + } + + @Override + public void onFinished(UriRequest request) { + try { + base.onFinished(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..2ef8fa7c758298dfe3201ed3bd92b8414d30d03f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpRequest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.http.annotation; + +import com.wordplat.quickstart.xutils.http.app.DefaultParamsBuilder; +import com.wordplat.quickstart.xutils.http.app.ParamsBuilder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * HttpRequest + * + * @since 2021-05-09 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HttpRequest { + /** + * host + * + * @return String + */ + String host() default ""; + + /** + * String + * + * @return String + */ + String path(); + + /** + * builder + * + * @return Class + */ + Class builder() default DefaultParamsBuilder.class; + + /** + * signs + * + * @return String + */ + String[] signs() default ""; + + /** + * cacheKeys + * + * @return String + */ + String[] cacheKeys() default ""; +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpResponse.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..52dd597ca77c5488522c2d39a8dd0ed1401c2e3b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/annotation/HttpResponse.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.http.annotation; + +import com.wordplat.quickstart.xutils.http.app.ResponseParser; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * HttpResponse + * + * @since 2021-05-09 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HttpResponse { + /** + * parser + * + * @return Class + */ + Class parser(); +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultParamsBuilder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultParamsBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..1fb008b3cd782375d94e1f42de69df3006f71a03 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultParamsBuilder.java @@ -0,0 +1,109 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.annotation.HttpRequest; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Created by wyouflf on 15/8/20. + * 默认参数构造器 + * + * @since 2021-05-09 + */ +public class DefaultParamsBuilder implements ParamsBuilder { + private static SSLSocketFactory trustAllSSlSocketFactory; + + /** + * DefaultParamsBuilder + */ + public DefaultParamsBuilder() { + } + + @Override + public String buildUri(RequestParams params, HttpRequest httpRequest) throws Throwable { + return httpRequest.host() + "/" + httpRequest.path(); + } + + @Override + public String buildCacheKey(RequestParams params, String[] cacheKeys) { + StringBuilder result = new StringBuilder(); + if (cacheKeys != null && cacheKeys.length > 0) { + result.append(params.getUri()).append("?"); + + // 添加cacheKeys对应的参数 + for (String key : cacheKeys) { + List kvList = params.getParams(key); + if (kvList != null && !kvList.isEmpty()) { + for (KeyValue kv : kvList) { + String value = kv.getValueStrOrNull(); + if (value != null) { + result.append(key).append("=").append(value).append("&"); + } + } + } + } + } + return result.toString(); + } + + @Override + public SSLSocketFactory getSSLSocketFactory() throws Throwable { + return getTrustAllSSLSocketFactory(); + } + + @Override + public void buildParams(RequestParams params) throws Throwable { + } + + @Override + public void buildSign(RequestParams params, String[] signs) throws Throwable { + } + + /** + * getTrustAllSSLSocketFactory + * + * @return SSLSocketFactory + */ + public static SSLSocketFactory getTrustAllSSLSocketFactory() { + if (trustAllSSlSocketFactory == null) { + synchronized (DefaultParamsBuilder.class) { + if (trustAllSSlSocketFactory == null) { + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + LogUtil.d("checkClientTrusted:" + authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + LogUtil.d("checkServerTrusted:" + authType); + } + } + }; + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, null); + trustAllSSlSocketFactory = sslContext.getSocketFactory(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + return trustAllSSlSocketFactory; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultRedirectHandler.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultRedirectHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d5e311fdd5ad6d71c33515600dea7fcbfcbd2ebb --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/DefaultRedirectHandler.java @@ -0,0 +1,52 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.common.util.URLUtil; +import com.wordplat.quickstart.xutils.http.HttpMethod; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.HttpRequest; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * DefaultRedirectHandler + * + * @since 2021-05-09 + */ +public class DefaultRedirectHandler implements RedirectHandler { + @Override + public RequestParams getRedirectParams(UriRequest request) throws Throwable { + if (request instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) request; + RequestParams params = httpRequest.getParams(); + String location = httpRequest.getResponseHeader("Location"); + if (!TextUtils.isEmpty(location)) { + if (!URLUtil.isHttpsUrl(location) && !URLUtil.isHttpUrl(location)) { + String url = params.getUri(); + if (location.startsWith("/")) { + int pathIndex = url.indexOf("/", 8); + if (pathIndex != -1) { + url = url.substring(0, pathIndex); + } + } else { + int pathIndex = url.lastIndexOf("/"); + if (pathIndex >= 8) { + url = url.substring(0, pathIndex + 1); + } else { + url += "/"; + } + } + location = url + location; + } + params.setUri(location); + + int code = request.getResponseCode(); + if (code == 301 || code == 302 || code == 303) { + params.clearParams(); + params.setMethod(HttpMethod.GET); + return params; + } + } + } + return null; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/HttpRetryHandler.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/HttpRetryHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..80e6fed3218066068bb902b8f8ca01cbe0c1f929 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/HttpRetryHandler.java @@ -0,0 +1,80 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.json.JSONException; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.ex.HttpException; +import com.wordplat.quickstart.xutils.http.HttpMethod; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +import java.io.FileNotFoundException; +import java.net.*; +import java.util.HashSet; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +public class HttpRetryHandler { + protected static HashSet> blackList = new HashSet>(); + protected int maxRetryCount = 2; + + static { + blackList.add(HttpException.class); + blackList.add(Callback.CancelledException.class); + blackList.add(MalformedURLException.class); + blackList.add(URISyntaxException.class); + blackList.add(NoRouteToHostException.class); + blackList.add(PortUnreachableException.class); + blackList.add(ProtocolException.class); + blackList.add(NullPointerException.class); + blackList.add(FileNotFoundException.class); + blackList.add(JSONException.class); + blackList.add(UnknownHostException.class); + blackList.add(IllegalArgumentException.class); + } + + /** + * HttpRetryHandler + */ + public HttpRetryHandler() { + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + /** + * canRetry + * + * @param request + * @param ex + * @param count + * @return boolean + */ + public boolean canRetry(UriRequest request, Throwable ex, int count) { + LogUtil.w(ex.getMessage(), ex); + + if (count > maxRetryCount) { + LogUtil.w(request.toString()); + LogUtil.w("The Max Retry times has been reached!"); + return false; + } + + if (!HttpMethod.permitsRetry(request.getParams().getMethod())) { + LogUtil.w(request.toString()); + LogUtil.w("The Request Method can not be retried."); + return false; + } + + if (blackList.contains(ex.getClass())) { + LogUtil.w(request.toString()); + LogUtil.w("The Exception can not be retried."); + return false; + } + + return true; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ParamsBuilder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ParamsBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..56ecb44f2f89e1d94394a11f7e472bb6af8075ba --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ParamsBuilder.java @@ -0,0 +1,58 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.annotation.HttpRequest; + +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by wyouflf on 15/8/20. + *

+ * + * @since 2021-05-09 + */ +public interface ParamsBuilder { + /** + * buildUri + * + * @param params + * @param httpRequest + * @return String + * @throws Throwable + */ + String buildUri(RequestParams params, HttpRequest httpRequest) throws Throwable; + + /** + * buildCacheKey + * + * @param params + * @param cacheKeys + * @return String + */ + String buildCacheKey(RequestParams params, String[] cacheKeys); + + /** + * getSSLSocketFactory + * + * @return SSLSocketFactory + * @throws Throwable + */ + SSLSocketFactory getSSLSocketFactory() throws Throwable; + + /** + * buildParams + * + * @param params + * @throws Throwable + */ + void buildParams(RequestParams params) throws Throwable; + + /** + * buildSign + * + * @param params + * @param signs + * @throws Throwable + */ + void buildSign(RequestParams params, String[] signs) throws Throwable; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RedirectHandler.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RedirectHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..cf16633f7644f8294bd2dcc4426fe30c8a0a2de0 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RedirectHandler.java @@ -0,0 +1,21 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/12. + * 请求重定向控制接口 + * + * @since 2021-05-09 + */ +public interface RedirectHandler { + /** + * getRedirectParams + * + * @param request + * @return RequestParams + * @throws Throwable + */ + RequestParams getRedirectParams(UriRequest request) throws Throwable; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestInterceptListener.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestInterceptListener.java new file mode 100644 index 0000000000000000000000000000000000000000..b8d9955137c80eefb7168d8e4b173f7355a3aaef --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestInterceptListener.java @@ -0,0 +1,31 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/10. + * 拦截请求响应(在后台线程工作). + *

+ * 用法: + * 1. 请求的callback参数同时实现RequestInterceptListener + * 2. 或者使用 @HttpRequest 注解实现ParamsBuilder接口 + * + * @since 2021-05-09 + */ +public interface RequestInterceptListener { + /** + * beforeRequest + * + * @param request + * @throws Throwable + */ + void beforeRequest(UriRequest request) throws Throwable; + + /** + * afterRequest + * + * @param request + * @throws Throwable + */ + void afterRequest(UriRequest request) throws Throwable; +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestTracker.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..b1b1e7f724358d5628682e7fad2a0017ded75fcd --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/RequestTracker.java @@ -0,0 +1,78 @@ +package com.wordplat.quickstart.xutils.http.app; + +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/9/10. + * 请求过程追踪, 适合用来记录请求日志. + * 所有回调方法都在主线程进行. + *

+ * 用法: + * 1. 将RequestTracker实例设置给请求参数RequestParams. + * 2. 请的callback参数同时实现RequestTracker接口; + * 3. 注册给UriRequestFactory的默认RequestTracker. + * 注意: 请求回调RequestTracker时优先级按照上面的顺序, + * 找到一个RequestTracker的实现会忽略其他. + */ +public interface RequestTracker { + /** + * onWaiting + * + * @param params + */ + void onWaiting(RequestParams params); + + /** + * onStart + * + * @param params + */ + void onStart(RequestParams params); + + /** + * onRequestCreated + * + * @param request + */ + void onRequestCreated(UriRequest request); + + /** + * onCache + * + * @param request + * @param result + */ + void onCache(UriRequest request, Object result); + + /** + * onSuccess + * + * @param request + * @param result + */ + void onSuccess(UriRequest request, Object result); + + /** + * onCancelled + * + * @param request + */ + void onCancelled(UriRequest request); + + /** + * onError + * + * @param request + * @param ex + * @param isCallbackError + */ + void onError(UriRequest request, Throwable ex, boolean isCallbackError); + + /** + * onFinished + * + * @param request + */ + void onFinished(UriRequest request); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ResponseParser.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ResponseParser.java new file mode 100644 index 0000000000000000000000000000000000000000..66b4506239b7f9b3f7fc30bb55d6214f49a4825a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/app/ResponseParser.java @@ -0,0 +1,22 @@ +package com.wordplat.quickstart.xutils.http.app; + +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/8/4. + * + * @param 支持String, byte[], JSONObject, JSONArray, InputStream + * @since 2021-05-09 + */ +public interface ResponseParser extends RequestInterceptListener { + /** + * parse + * + * @param resultType + * @param resultClass + * @param result + * @return Object + * @throws Throwable + */ + Object parse(Type resultType, Class resultClass, ResponseDataType result) throws Throwable; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/FileBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/FileBody.java new file mode 100644 index 0000000000000000000000000000000000000000..32f1b7b90a5976fbbb638d2e5b645a869152e22a --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/FileBody.java @@ -0,0 +1,79 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import ohos.utils.net.Uri; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.HttpURLConnection; + +/** + * Created by wyouflf on 15/8/13. + * + * @since 2021-05-09 + */ +public class FileBody extends InputStreamBody { + private File file; + private String contentType; + + /** + * FileBody + * + * @param file + * @throws IOException + */ + public FileBody(File file) throws IOException { + this(file, null); + } + + /** + * FileBody + * + * @param file + * @param contentType + * @throws IOException + */ + public FileBody(File file, String contentType) throws IOException { + super(new FileInputStream(file)); + this.file = file; + this.contentType = contentType; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + if (TextUtils.isEmpty(contentType)) { + contentType = getFileContentType(file); + } + return contentType; + } + + /** + * getFileContentType + * + * @param file + * @return String + */ + public static String getFileContentType(File file) { + String filename = file.getName(); + String contentType = null; + try { + filename = Uri.encode(filename, "-![.:/,?&=]"); + contentType = HttpURLConnection.guessContentTypeFromName(filename); + } catch (Exception e) { + LogUtil.e(e.toString()); + } + if (TextUtils.isEmpty(contentType)) { + contentType = "application/octet-stream"; + } else { + contentType = contentType.replaceFirst("\\/jpg$", "/jpeg"); + } + return contentType; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/InputStreamBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/InputStreamBody.java new file mode 100644 index 0000000000000000000000000000000000000000..02529fbd638b467cb71e82b1da8f113b5fa08dc8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/InputStreamBody.java @@ -0,0 +1,110 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.ProgressHandler; + +import java.io.*; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +public class InputStreamBody implements ProgressBody { + private InputStream content; + private String contentType; + + private final long total; + private long current = 0; + + private ProgressHandler callBackHandler; + + /** + * InputStreamBody + * + * @param inputStream + */ + public InputStreamBody(InputStream inputStream) { + this(inputStream, null); + } + + /** + * InputStreamBody + * + * @param inputStream + * @param contentType + */ + public InputStreamBody(InputStream inputStream, String contentType) { + this.content = inputStream; + this.contentType = contentType; + this.total = getInputStreamLength(inputStream); + } + + @Override + public void setProgressHandler(ProgressHandler progressHandler) { + this.callBackHandler = progressHandler; + } + + @Override + public long getContentLength() { + return total; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + return TextUtils.isEmpty(contentType) ? "application/octet-stream" : contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("upload stopped!"); + } + + byte[] buffer = new byte[4096]; + try { + int len = 0; + while ((len = content.read(buffer)) != -1) { + out.write(buffer, 0, len); + current += len; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + out.flush(); + + if (callBackHandler != null) { + callBackHandler.updateProgress(total, current, true); + } + } finally { + IOUtil.closeQuietly(content); + } + } + + /** + * getInputStreamLength + * + * @param inputStream + * @return long + */ + public static long getInputStreamLength(InputStream inputStream) { + try { + if (inputStream instanceof FileInputStream || + inputStream instanceof ByteArrayInputStream) { + return inputStream.available(); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + return -1L; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/MultipartBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/MultipartBody.java new file mode 100644 index 0000000000000000000000000000000000000000..713102bc7dedad5b63e8f38a6bea4cfedc44f7da --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/MultipartBody.java @@ -0,0 +1,274 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.BaseParams; +import com.wordplat.quickstart.xutils.http.BaseParams.BodyItemWrapper; +import com.wordplat.quickstart.xutils.http.ProgressHandler; + +import java.io.*; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +public class MultipartBody implements ProgressBody { + private static byte[] BOUNDARY_PREFIX_BYTES = "--------7da3d81520810".getBytes(); + private static byte[] END_BYTES = "\r\n".getBytes(); + private static byte[] TWO_DASHES_BYTES = "--".getBytes(); + private byte[] boundaryPostfixBytes; + private String contentType; // multipart/subtype; boundary=xxx... + private String charset = "UTF-8"; + private ProgressHandler callBackHandler; + private List multipartParams; + private long total = 0; + private long current = 0; + + /** + * \ + * MultipartBody + * + * @param multipartParams + * @param charset + */ + public MultipartBody(List multipartParams, String charset) { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + this.multipartParams = multipartParams; + generateContentType(); + + // calc total + CounterOutputStream counter = new CounterOutputStream(); + try { + this.writeTo(counter); + this.total = counter.total.get(); + } catch (IOException e) { + this.total = -1; + } + } + + @Override + public void setProgressHandler(ProgressHandler progressHandler) { + this.callBackHandler = progressHandler; + } + + private void generateContentType() { + String boundaryPostfix = Double.toHexString(Math.random() * 0xFFFF); + boundaryPostfixBytes = boundaryPostfix.getBytes(); + contentType = "multipart/form-data; boundary=" + new String(BOUNDARY_PREFIX_BYTES) + boundaryPostfix; + } + + @Override + public long getContentLength() { + return total; + } + + /** + * only change subType: + * "multipart/subType; boundary=xxx..." + * + * @param subType "form-data" or "related" + */ + @Override + public void setContentType(String subType) { + if (TextUtils.isEmpty(subType)) { + return; + } + int index = contentType.indexOf(";"); + this.contentType = "multipart/" + subType + contentType.substring(index); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("upload stopped!"); + } + + for (KeyValue entry : multipartParams) { + writeEntry(out, entry); + } + writeLine(out, TWO_DASHES_BYTES, BOUNDARY_PREFIX_BYTES, boundaryPostfixBytes, TWO_DASHES_BYTES); + out.flush(); + + if (callBackHandler != null) { + callBackHandler.updateProgress(total, current, true); + } + } + + private void writeEntry(OutputStream out, KeyValue entry) throws IOException { + String name = entry.key; + Object value = entry.value; + if (TextUtils.isEmpty(name) || value == null) { + return; + } + + writeLine(out, TWO_DASHES_BYTES, BOUNDARY_PREFIX_BYTES, boundaryPostfixBytes); + + String fileName = ""; + String contentType = null; + if (entry instanceof BodyItemWrapper) { + BaseParams.BodyItemWrapper wrapper = (BaseParams.BodyItemWrapper) entry; + fileName = wrapper.fileName; + contentType = wrapper.contentType; + } + + if (value instanceof File) { + File file = (File) value; + if (TextUtils.isEmpty(fileName)) { + fileName = file.getName(); + } + if (TextUtils.isEmpty(contentType)) { + contentType = FileBody.getFileContentType(file); + } + writeLine(out, buildContentDisposition(name, fileName, charset)); + writeLine(out, buildContentType(value, contentType, charset)); + writeLine(out); // 内容前空一行 + writeFile(out, file); + writeLine(out); + } else { + writeLine(out, buildContentDisposition(name, fileName, charset)); + writeLine(out, buildContentType(value, contentType, charset)); + writeLine(out); // 内容前空一行 + if (value instanceof InputStream) { + writeStreamAndCloseIn(out, (InputStream) value); + writeLine(out); + } else { + byte[] content; + if (value instanceof byte[]) { + content = (byte[]) value; + } else { + content = entry.getValueStrOrEmpty().getBytes(charset); + } + writeLine(out, content); + current += content.length; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + } + } + + private void writeLine(OutputStream out, byte[]... bs) throws IOException { + if (bs != null) { + for (byte[] b : bs) { + out.write(b); + } + } + out.write(END_BYTES); + } + + private void writeFile(OutputStream out, File file) throws IOException { + if (out instanceof CounterOutputStream) { + ((CounterOutputStream) out).addFile(file); + } else { + writeStreamAndCloseIn(out, new FileInputStream(file)); + } + } + + private void writeStreamAndCloseIn(OutputStream out, InputStream in) throws IOException { + if (out instanceof CounterOutputStream) { + ((CounterOutputStream) out).addStream(in); + } else { + try { + int len; + byte[] buf = new byte[4096]; + while ((len = in.read(buf)) >= 0) { + out.write(buf, 0, len); + current += len; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + } finally { + IOUtil.closeQuietly(in); + } + } + } + + private static byte[] buildContentDisposition(String name, String fileName, String charset) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder("Content-Disposition: form-data"); + result.append("; name=\"").append(name.replace("\"", "\\\"")).append("\""); + if (!TextUtils.isEmpty(fileName)) { + result.append("; filename=\"").append(fileName.replace("\"", "\\\"")).append("\""); + } + return result.toString().getBytes(charset); + } + + private static byte[] buildContentType(Object value, String contentType, String charset) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder("Content-Type: "); + if (TextUtils.isEmpty(contentType)) { + if (value instanceof String) { + contentType = "text/plain; charset=" + charset; + } else { + contentType = "application/octet-stream"; + } + } else { + contentType = contentType.replaceFirst("\\/jpg$", "/jpeg"); + } + result.append(contentType); + return result.toString().getBytes(charset); + } + + private class CounterOutputStream extends OutputStream { + final AtomicLong total = new AtomicLong(0L); + + /** + * CounterOutputStream + */ + public CounterOutputStream() { + } + + public void addFile(File file) { + if (total.get() == -1L) { + return; + } + total.addAndGet(file.length()); + } + + public void addStream(InputStream inputStream) { + if (total.get() == -1L) return; + long length = InputStreamBody.getInputStreamLength(inputStream); + if (length > 0) { + total.addAndGet(length); + } else { + total.set(-1L); + } + } + + @Override + public void write(int oneByte) throws IOException { + if (total.get() == -1L) { + return; + } + total.incrementAndGet(); + } + + @Override + public void write(byte[] buffer) throws IOException { + if (total.get() == -1L) { + return; + } + total.addAndGet(buffer.length); + } + + @Override + public void write(byte[] buffer, int offset, int count) throws IOException { + if (total.get() == -1L) { + return; + } + total.addAndGet(count); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/ProgressBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/ProgressBody.java new file mode 100644 index 0000000000000000000000000000000000000000..db5b7578e4d1fc9586e48166eb31f79c75567c25 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/ProgressBody.java @@ -0,0 +1,17 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.http.ProgressHandler; + +/** + * Created by wyouflf on 15/8/13. + * + * @since 2021-05-09 + */ +public interface ProgressBody extends RequestBody { + /** + * setProgressHandler + * + * @param progressHandler + */ + void setProgressHandler(ProgressHandler progressHandler); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/RequestBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/RequestBody.java new file mode 100644 index 0000000000000000000000000000000000000000..8857c253383f2c465d2c6a69f14c0ff7e2ed064c --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/RequestBody.java @@ -0,0 +1,40 @@ +package com.wordplat.quickstart.xutils.http.body; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Created by wyouflf on 15/10/29. + * + * @since 2021-05-09 + */ +public interface RequestBody { + /** + * getContentLength + * + * @return long + */ + long getContentLength(); + + /** + * setContentType + * + * @param contentType + */ + void setContentType(String contentType); + + /** + * getContentType + * + * @return String + */ + String getContentType(); + + /** + * writeTo + * + * @param out + * @throws IOException + */ + void writeTo(OutputStream out) throws IOException; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/StringBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/StringBody.java new file mode 100644 index 0000000000000000000000000000000000000000..7e5fd55c3dfad260556404af561e5071708940fa --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/StringBody.java @@ -0,0 +1,54 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.common.util.TextUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +public class StringBody implements RequestBody { + private byte[] content; + private String contentType; + private String charset = "UTF-8"; + + /** + * StringBody + * + * @param str + * @param charset + * @throws UnsupportedEncodingException + */ + public StringBody(String str, String charset) throws UnsupportedEncodingException { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + this.content = str.getBytes(this.charset); + } + + @Override + public long getContentLength() { + return content.length; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + return TextUtils.isEmpty(contentType) ? "application/json;charset=" + charset : contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + out.write(content); + out.flush(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/UrlEncodedBody.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/UrlEncodedBody.java new file mode 100644 index 0000000000000000000000000000000000000000..45579097ecf0525a532d337afbff92164a5f2ca2 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/body/UrlEncodedBody.java @@ -0,0 +1,74 @@ +package com.wordplat.quickstart.xutils.http.body; + +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.List; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +public class UrlEncodedBody implements RequestBody { + private byte[] content; + private String charset = "UTF-8"; + + /** + * UrlEncodedBody + * + * @param params + * @param charset + * @throws IOException + */ + public UrlEncodedBody(List params, String charset) throws IOException { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + StringBuilder contentSb = new StringBuilder(); + if (params != null) { + for (KeyValue kv : params) { + String name = kv.key; + String value = kv.getValueStrOrNull(); + if (!TextUtils.isEmpty(name) && value != null) { + if (contentSb.length() > 0) { + contentSb.append("&"); + } + contentSb.append(URLEncoder.encode(name, this.charset).replaceAll("\\+", "%20")) + .append("=") + .append(URLEncoder.encode(value, this.charset).replaceAll("\\+", "%20")); + } + } + } + + this.content = contentSb.toString().getBytes(this.charset); + } + + @Override + public long getContentLength() { + return content.length; + } + + @Override + public void setContentType(String contentType) { + if (!TextUtils.isEmpty(contentType)) { + LogUtil.w("ignored Content-Type: " + contentType); + } + } + + @Override + public String getContentType() { + return "application/x-www-form-urlencoded;charset=" + charset; + } + + @Override + public void writeTo(OutputStream sink) throws IOException { + sink.write(this.content); + sink.flush(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/CookieEntity.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/CookieEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..6cca900b8558e2a9a5764de42456033be0e45ac8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/CookieEntity.java @@ -0,0 +1,128 @@ +package com.wordplat.quickstart.xutils.http.cookie; + +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.db.annotation.Column; +import com.wordplat.quickstart.xutils.db.annotation.Table; + +import java.net.HttpCookie; +import java.net.URI; + +/** + * Created by wyouflf on 15/8/20. + * 数据库中的cookie实体 + */ +/** + * CookieEntity + * + * @since 2021-05-09 + */ +@Table(name = "cookie", + onCreated = "CREATE UNIQUE INDEX index_cookie_unique ON cookie(\"name\",\"domain\",\"path\")") +final class CookieEntity { + private static final long MAX_EXPIRY = System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 30L * 12L * 100L; + + @Column(name = "id", isId = true) + private long id; + + @Column(name = "uri") + private String uri; // cookie add by this uri. + + @Column(name = "name") + private String name; + @Column(name = "value") + private String value; + @Column(name = "comment") + private String comment; + @Column(name = "commentURL") + private String commentURL; + @Column(name = "discard") + private boolean discard; + @Column(name = "domain") + private String domain; + @Column(name = "expiry") + private long expiry = MAX_EXPIRY; + @Column(name = "path") + private String path; + @Column(name = "portList") + private String portList; + @Column(name = "secure") + private boolean secure; + @Column(name = "version") + private int version = 1; + + /** + * CookieEntity + */ + public CookieEntity() { + } + + /** + * CookieEntity + * + * @param uri + * @param cookie + */ + public CookieEntity(URI uri, HttpCookie cookie) { + this.uri = uri == null ? null : uri.toString(); + this.name = cookie.getName(); + this.value = cookie.getValue(); + this.comment = cookie.getComment(); + this.commentURL = cookie.getCommentURL(); + this.discard = cookie.getDiscard(); + this.domain = cookie.getDomain(); + long maxAge = cookie.getMaxAge(); + if (maxAge > 0) { + this.expiry = (maxAge * 1000L) + System.currentTimeMillis(); + if (this.expiry < 0L) { // 计算溢出? + this.expiry = MAX_EXPIRY; + } + } else { + this.expiry = -1L; + } + this.path = cookie.getPath(); + if (!TextUtils.isEmpty(path) && path.length() > 1 && path.endsWith("/")) { + this.path = path.substring(0, path.length() - 1); + } + this.portList = cookie.getPortlist(); + this.secure = cookie.getSecure(); + this.version = cookie.getVersion(); + } + + public HttpCookie toHttpCookie() { + HttpCookie cookie = new HttpCookie(name, value); + cookie.setComment(comment); + cookie.setCommentURL(commentURL); + cookie.setDiscard(discard); + cookie.setDomain(domain); + if (expiry == -1L) { + cookie.setMaxAge(-1L); + } else { + cookie.setMaxAge((expiry - System.currentTimeMillis()) / 1000L); + } + cookie.setPath(path); + cookie.setPortlist(portList); + cookie.setSecure(secure); + cookie.setVersion(version); + return cookie; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public boolean isExpired() { + return expiry != -1L && expiry < System.currentTimeMillis(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/DbCookieStore.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/DbCookieStore.java new file mode 100644 index 0000000000000000000000000000000000000000..5a0b84911d3bcb01e0945acd2e1d682eee596629 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/cookie/DbCookieStore.java @@ -0,0 +1,304 @@ +package com.wordplat.quickstart.xutils.http.cookie; + +import com.wordplat.quickstart.xutils.DbManager; +import com.wordplat.quickstart.xutils.common.task.PriorityExecutor; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.config.DbConfigs; +import com.wordplat.quickstart.xutils.db.Selector; +import com.wordplat.quickstart.xutils.db.sqlite.WhereBuilder; +import com.wordplat.quickstart.xutils.db.table.DbModel; +import com.wordplat.quickstart.xutils.x; + +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Created by wyouflf on 15/8/20. + * 基于数据库的CookieStore实现. + * + * @since 2021-05-09 + */ +public enum DbCookieStore implements CookieStore { + /** + * INSTANCE + */ + INSTANCE; + private static final long TRIM_TIME_SPAN = 1000; + /** + * 限制最多5000条数据 + */ + private static final int LIMIT_COUNT = 5000; + + private DbManager db; + private final Executor trimExecutor = new PriorityExecutor(1, true); + + private long lastTrimTime = 0L; + + DbCookieStore() { + x.task().run(new Runnable() { + @Override + public void run() { + tryInit(); + } + }); + } + + private void tryInit() { + if (db == null) { + synchronized (this) { + if (db == null) { + try { + db = x.getDb(DbConfigs.COOKIE.getConfig()); + db.delete(CookieEntity.class, + WhereBuilder.b("expiry", "=", -1L)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } + + @Override + public void add(URI uri, HttpCookie cookie) { + if (cookie == null) { + return; + } + + tryInit(); + + uri = getEffectiveURI(uri); + + try { + db.replace(new CookieEntity(uri, cookie)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } + + @Override + public List get(URI uri) { + if (uri == null) { + throw new NullPointerException("uri is null"); + } + + tryInit(); + + uri = getEffectiveURI(uri); + + List rt = new ArrayList(); + + try { + Selector selector = db.selector(CookieEntity.class); + + WhereBuilder where = WhereBuilder.b(); + + String host = uri.getHost(); + if (!TextUtils.isEmpty(host)) { + WhereBuilder subWhere = WhereBuilder.b("domain", "=", host).or("domain", "=", "." + host); + int firstDot = host.indexOf("."); + int lastDot = host.lastIndexOf("."); + if (firstDot > 0 && lastDot > firstDot) { + String domain = host.substring(firstDot, host.length()); + if (!TextUtils.isEmpty(domain)) { + subWhere.or("domain", "=", domain); + } + } + where.and(subWhere); + } + + String path = uri.getPath(); + if (!TextUtils.isEmpty(path)) { + WhereBuilder subWhere = WhereBuilder.b("path", "=", path) + .or("path", "=", "/").or("path", "=", null); + int lastSplit = path.lastIndexOf("/"); + while (lastSplit > 0) { + path = path.substring(0, lastSplit); + subWhere.or("path", "=", path); + lastSplit = path.lastIndexOf("/"); + } + + where.and(subWhere); + } + + where.or("uri", "=", uri.toString()); + + List cookieEntityList = selector.where(where).findAll(); + if (cookieEntityList != null) { + for (CookieEntity cookieEntity : cookieEntityList) { + if (!cookieEntity.isExpired()) { + rt.add(cookieEntity.toHttpCookie()); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return rt; + } + + @Override + public List getCookies() { + tryInit(); + + List rt = new ArrayList(); + + try { + List cookieEntityList = db.findAll(CookieEntity.class); + if (cookieEntityList != null) { + for (CookieEntity cookieEntity : cookieEntityList) { + if (!cookieEntity.isExpired()) { + rt.add(cookieEntity.toHttpCookie()); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return rt; + } + + @Override + public List getURIs() { + tryInit(); + + List uris = new ArrayList(); + + try { + List uriList = + db.selector(CookieEntity.class).select("uri").findAll(); + if (uriList != null) { + for (DbModel model : uriList) { + String uri = model.getString("uri"); + if (!TextUtils.isEmpty(uri)) { + try { + uris.add(new URI(uri)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + try { + db.delete(CookieEntity.class, WhereBuilder.b("uri", "=", uri)); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + return uris; + } + + @Override + public boolean remove(URI uri, HttpCookie cookie) { + if (cookie == null) { + return true; + } + + tryInit(); + + boolean modified = false; + try { + WhereBuilder where = WhereBuilder.b("name", "=", cookie.getName()); + + String domain = cookie.getDomain(); + if (!TextUtils.isEmpty(domain)) { + where.and("domain", "=", domain); + } + + String path = cookie.getPath(); + if (!TextUtils.isEmpty(path)) { + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + where.and("path", "=", path); + } + + db.delete(CookieEntity.class, where); + + modified = true; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + return modified; + } + + @Override + public boolean removeAll() { + tryInit(); + + try { + db.delete(CookieEntity.class); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return true; + } + + private void trimSize() { + trimExecutor.execute(new Runnable() { + @Override + public void run() { + tryInit(); + + long current = System.currentTimeMillis(); + if (current - lastTrimTime < TRIM_TIME_SPAN) { + return; + } else { + lastTrimTime = current; + } + + // delete expires + try { + db.delete(CookieEntity.class, WhereBuilder + .b("expiry", "<", System.currentTimeMillis()) + .and("expiry", "!=", -1L)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + // trim by limit count + try { + int count = (int) db.selector(CookieEntity.class).count(); + if (count > LIMIT_COUNT + 10) { + List rmList = db.selector(CookieEntity.class) + .where("expiry", "!=", -1L).orderBy("expiry") + .limit(count - LIMIT_COUNT).findAll(); + if (rmList != null) { + db.delete(rmList); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + private URI getEffectiveURI(final URI uri) { + URI effectiveURI = null; + try { + effectiveURI = new URI("http", + uri.getHost(), + uri.getPath(), + null, + null + ); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + effectiveURI = uri; + } + + return effectiveURI; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/BooleanLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/BooleanLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..c5b2cc8cb833f16fd92d926769b0617004fd65a9 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/BooleanLoader.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +class BooleanLoader extends Loader { + @Override + public Loader newInstance() { + return new BooleanLoader(); + } + + @Override + public Boolean load(final UriRequest request) throws Throwable { + request.sendRequest(); + return request.getResponseCode() < 300; + } + + @Override + public Boolean loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ByteArrayLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ByteArrayLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..d6dee3bff8929090de4f7617995f6ff2fc937d09 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ByteArrayLoader.java @@ -0,0 +1,43 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +class ByteArrayLoader extends Loader { + private byte[] resultData; + + @Override + public Loader newInstance() { + return new ByteArrayLoader(); + } + + @Override + public byte[] load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultData = IOUtil.readBytes(request.getInputStream()); + return resultData; + } + + @Override + public byte[] loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + byte[] data = cacheEntity.getBytesContent(); + if (data != null && data.length > 0) { + return data; + } + } + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + saveByteArrayCache(request, resultData); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/FileLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/FileLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..6042a05f686b7692779f37e3da38640b9967938b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/FileLoader.java @@ -0,0 +1,345 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.cache.DiskCacheFile; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.ProcessLock; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.ex.FileLockedException; +import com.wordplat.quickstart.xutils.ex.HttpException; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +import java.io.*; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Date; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * 下载参数策略: + * 1. RequestParams#saveFilePath不为空时, 目标文件保存在saveFilePath; + * 否则由Cache策略分配文件下载路径. + * 2. 下载时临时目标文件路径为tempSaveFilePath, 下载完后进行a: CacheFile#commit; b:重命名等操作. + * 断点下载策略: + * 1. 要下载的目标文件不存在或小于 CHECK_SIZE 时删除目标文件, 重新下载. + * 2. 若文件存在且大于 CHECK_SIZE, range = fileLen - CHECK_SIZE , 校验check_buffer, 相同: 继续下载; + * 不相同: 删掉目标文件, 并抛出RuntimeException(HttpRetryHandler会使下载重新开始). + * + * @since 2021-05-09 + */ +public class FileLoader extends Loader { + private static final int CHECK_SIZE = 512; + + private RequestParams params; + private String tempSaveFilePath; + private String saveFilePath; + private boolean isAutoResume; + private boolean isAutoRename; + private long contentLength; + private String responseFileName; + + private DiskCacheFile diskCacheFile; + + @Override + public Loader newInstance() { + return new FileLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + this.params = params; + isAutoResume = params.isAutoResume(); + isAutoRename = params.isAutoRename(); + } + } + + /** + * load + * + * @param in + * @return File + * @throws Throwable + * @throws IOException + * @throws RuntimeException + * @throws Callback.CancelledException + */ + protected File load(final InputStream in) throws Throwable { + File targetFile = null; + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + try { + targetFile = new File(tempSaveFilePath); + if (targetFile.isDirectory()) { + throw new IOException("could not create the file: " + tempSaveFilePath); + } + if (!targetFile.exists()) { + File dir = targetFile.getParentFile(); + if ((!dir.exists() && !dir.mkdirs()) || !dir.isDirectory()) { + throw new IOException("could not create the dir: " + dir.getCanonicalPath()); + } + } + + // 处理[断点逻辑2](见文件头doc) + long targetFileLen = targetFile.length(); + if (isAutoResume && targetFileLen > 0) { + FileInputStream fis = null; + try { + long filePos = targetFileLen - CHECK_SIZE; + if (filePos > 0) { + fis = new FileInputStream(targetFile); + byte[] fileCheckBuffer = IOUtil.readBytes(fis, filePos, CHECK_SIZE); + byte[] checkBuffer = IOUtil.readBytes(in, 0, CHECK_SIZE); + if (!Arrays.equals(checkBuffer, fileCheckBuffer)) { + IOUtil.closeQuietly(fis); // 先关闭文件流, 否则文件删除会失败. + IOUtil.deleteFileOrDir(targetFile); + throw new RuntimeException("need retry"); + } else { + contentLength -= CHECK_SIZE; + } + } else { + IOUtil.deleteFileOrDir(targetFile); + throw new RuntimeException("need retry"); + } + } finally { + IOUtil.closeQuietly(fis); + } + } + + // 开始下载 + long current = 0; + FileOutputStream fileOutputStream = null; + if (isAutoResume) { + current = targetFileLen; + fileOutputStream = new FileOutputStream(targetFile, true); + } else { + fileOutputStream = new FileOutputStream(targetFile); + } + + long total = contentLength + current; + bis = new BufferedInputStream(in); + bos = new BufferedOutputStream(fileOutputStream); + + if (progressHandler != null && !progressHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("download stopped!"); + } + + byte[] tmp = new byte[4096]; + int len; + while ((len = bis.read(tmp)) != -1) { + if (!targetFile.getParentFile().exists()) { + targetFile.getParentFile().mkdirs(); + throw new IOException("parent be deleted!"); + } + + bos.write(tmp, 0, len); + current += len; + if (progressHandler != null) { + if (!progressHandler.updateProgress(total, current, false)) { + bos.flush(); + throw new Callback.CancelledException("download stopped!"); + } + } + } + bos.flush(); + if (diskCacheFile != null) { + targetFile = diskCacheFile.commit(); + } + + if (progressHandler != null) { + progressHandler.updateProgress(total, current, true); + } + } finally { + IOUtil.closeQuietly(bis); + IOUtil.closeQuietly(bos); + } + + return autoRename(targetFile); + } + + @Override + public File load(final UriRequest request) throws Throwable { + ProcessLock processLock = null; + File result = null; + try { + saveFilePath = params.getSaveFilePath(); + diskCacheFile = null; + if (TextUtils.isEmpty(saveFilePath)) { + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + initDiskCacheFile(request); + } else { + tempSaveFilePath = saveFilePath + ".tmp"; + } + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + // 等待, 若不能下载则取消此次下载. + processLock = ProcessLock.tryLock(saveFilePath + "_lock", true); + if (processLock == null || !processLock.isValid()) { + throw new FileLockedException("download exists: " + saveFilePath); + } + + params = request.getParams(); + long range = 0; + if (isAutoResume) { + File tempFile = new File(tempSaveFilePath); + long fileLen = tempFile.length(); + if (fileLen <= CHECK_SIZE) { + IOUtil.deleteFileOrDir(tempFile); + range = 0; + } else { + range = fileLen - CHECK_SIZE; + } + } + params.setHeader("Range", "bytes=" + range + "-"); + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + request.sendRequest(); // may be throw an HttpException + + contentLength = request.getContentLength(); + if (isAutoRename) { + responseFileName = getResponseFileName(request); + } + if (isAutoResume) { + isAutoResume = isSupportRange(request); + } + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + if (diskCacheFile != null) { + try { + DiskCacheEntity entity = diskCacheFile.getCacheEntity(); + entity.setLastAccess(System.currentTimeMillis()); + entity.setEtag(request.getETag()); + entity.setExpires(request.getExpiration()); + entity.setLastModify(new Date(request.getLastModified())); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + result = this.load(request.getInputStream()); + } catch (HttpException httpException) { + if (httpException.getCode() == 416) { + if (diskCacheFile != null) { + result = diskCacheFile.commit(); + } else { + result = new File(tempSaveFilePath); + } + if (result != null && result.exists()) { + if (isAutoRename) { + responseFileName = getResponseFileName(request); + } + result = autoRename(result); + } else { + IOUtil.deleteFileOrDir(result); + throw new IllegalStateException("cache file not found" + request.getCacheKey()); + } + } else { + throw httpException; + } + } finally { + IOUtil.closeQuietly(processLock); + IOUtil.closeQuietly(diskCacheFile); + } + return result; + } + + // 保存路径为空, 存入磁盘缓存. + private void initDiskCacheFile(final UriRequest request) throws Throwable { + DiskCacheEntity entity = new DiskCacheEntity(); + entity.setKey(request.getCacheKey()); + diskCacheFile = LruDiskCache.getDiskCache(params.getCacheDirName()).createDiskCacheFile(entity); + + if (diskCacheFile != null) { + saveFilePath = diskCacheFile.getAbsolutePath(); + tempSaveFilePath = saveFilePath; + isAutoRename = false; + } else { + throw new IOException("create cache file error:" + request.getCacheKey()); + } + } + + // 处理[下载逻辑2.b](见文件头doc) + private File autoRename(File loadedFile) { + if (isAutoRename && loadedFile.exists() && !TextUtils.isEmpty(responseFileName)) { + File newFile = new File(loadedFile.getParent(), responseFileName); + while (newFile.exists()) { + newFile = new File(loadedFile.getParent(), System.currentTimeMillis() + responseFileName); + } + return loadedFile.renameTo(newFile) ? newFile : loadedFile; + } else if (!saveFilePath.equals(tempSaveFilePath)) { + File newFile = new File(saveFilePath); + return loadedFile.renameTo(newFile) ? newFile : loadedFile; + } else { + return loadedFile; + } + } + + private static String getResponseFileName(UriRequest request) { + if (request == null) { + return null; + } + String disposition = request.getResponseHeader("Content-Disposition"); + if (!TextUtils.isEmpty(disposition)) { + int startIndex = disposition.indexOf("filename="); + if (startIndex > 0) { + startIndex += 9; // "filename=".length() + int endIndex = disposition.indexOf(";", startIndex); + if (endIndex < 0) { + endIndex = disposition.length(); + } + if (endIndex > startIndex) { + try { + String name = URLDecoder.decode( + disposition.substring(startIndex, endIndex), + request.getParams().getCharset()); + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + return name; + } catch (UnsupportedEncodingException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + return null; + } + + private static boolean isSupportRange(UriRequest request) { + if (request == null) { + return false; + } + String ranges = request.getResponseHeader("Accept-Ranges"); + if (ranges != null) { + return ranges.contains("bytes"); + } + ranges = request.getResponseHeader("Content-Range"); + return ranges != null && ranges.contains("bytes"); + } + + @Override + public File loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return LruDiskCache.getDiskCache(params.getCacheDirName()).getDiskCacheFile(cacheEntity.getKey()); + } + + @Override + public void save2Cache(final UriRequest request) { + // the file caches already saved by diskCacheFile#commit + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/InputStreamLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/InputStreamLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..7a080a8b1f296a87e002002d3246ebd7f784ec44 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/InputStreamLoader.java @@ -0,0 +1,36 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +import java.io.InputStream; + +/** + * 将PrepareType设置为InputStream, 以便在PrepareCallback#prepare中做耗时的数据任务处理. + *

+ * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +class InputStreamLoader extends Loader { + @Override + public Loader newInstance() { + return new InputStreamLoader(); + } + + @Override + public InputStream load(final UriRequest request) throws Throwable { + request.sendRequest(); + return request.getInputStream(); + } + + @Override + public InputStream loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/IntegerLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/IntegerLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..39f767525f3426ad8079d0373d3eee81835fb59d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/IntegerLoader.java @@ -0,0 +1,32 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/10/17 + * + * @since 2021-05-09 + */ +class IntegerLoader extends Loader { + @Override + public Loader newInstance() { + return new IntegerLoader(); + } + + @Override + public Integer load(UriRequest request) throws Throwable { + request.sendRequest(); + return request.getResponseCode(); + } + + @Override + public Integer loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(UriRequest request) { + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONArrayLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONArrayLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..7a6d9bf07b3563757b4287fe6553551c8dd5dd10 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONArrayLoader.java @@ -0,0 +1,58 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.json.JSONArray; +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/06/16 + * + * @since 2021-05-09 + */ +class JSONArrayLoader extends Loader { + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new JSONArrayLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public JSONArray load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return new JSONArray(resultStr); + } + + @Override + public JSONArray loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + String text = cacheEntity.getTextContent(); + if (!TextUtils.isEmpty(text)) { + return new JSONArray(text); + } + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONObjectLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONObjectLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..6600e5e9db0b09b0d90153e148b8819777fdc478 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/JSONObjectLoader.java @@ -0,0 +1,58 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.json.JSONObject; +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/06/16 + * + * @since 2021-05-09 + */ +class JSONObjectLoader extends Loader { + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new JSONObjectLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public JSONObject load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return new JSONObject(resultStr); + } + + @Override + public JSONObject loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + String text = cacheEntity.getTextContent(); + if (!TextUtils.isEmpty(text)) { + return new JSONObject(text); + } + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/Loader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/Loader.java new file mode 100644 index 0000000000000000000000000000000000000000..ae96e741c9f12b7f65a56bf82d8452fdf1a3422d --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/Loader.java @@ -0,0 +1,98 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.ProgressHandler; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +import java.util.Date; + +/** + * Author: wyouflf + * Time: 2014/05/26 + * + * @since 2021-05-09 + */ +public abstract class Loader { + protected ProgressHandler progressHandler; + + /** + * setParams + * + * @param params + */ + public void setParams(final RequestParams params) { + } + + public void setProgressHandler(final ProgressHandler callbackHandler) { + this.progressHandler = callbackHandler; + } + + /** + * saveStringCache + * + * @param request + * @param resultStr + */ + protected void saveStringCache(UriRequest request, String resultStr) { + saveCacheInternal(request, resultStr, null); + } + + /** + * saveByteArrayCache + * + * @param request + * @param resultData + */ + protected void saveByteArrayCache(UriRequest request, byte[] resultData) { + saveCacheInternal(request, null, resultData); + } + + /** + * newInstance + * + * @return Loader + */ + public abstract Loader newInstance(); + + /** + * load + * + * @param request + * @return T + * @throws Throwable + */ + public abstract T load(final UriRequest request) throws Throwable; + + /** + * loadFromCache + * + * @param cacheEntity + * @return T + * @throws Throwable + */ + public abstract T loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable; + + /** + * save2Cache + * + * @param request + */ + public abstract void save2Cache(final UriRequest request); + + private void saveCacheInternal(UriRequest request, String resultStr, byte[] resultData) { + if (!TextUtils.isEmpty(resultStr) || (resultData != null && resultData.length > 0)) { + DiskCacheEntity entity = new DiskCacheEntity(); + entity.setKey(request.getCacheKey()); + entity.setLastAccess(System.currentTimeMillis()); + entity.setEtag(request.getETag()); + entity.setExpires(request.getExpiration()); + entity.setLastModify(new Date(request.getLastModified())); + entity.setTextContent(resultStr); + entity.setBytesContent(resultData); + LruDiskCache.getDiskCache(request.getParams().getCacheDirName()).put(entity); + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/LoaderFactory.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/LoaderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..ec423fdefb0ab2bbf70cd95762e56038d1503213 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/LoaderFactory.java @@ -0,0 +1,67 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.json.JSONArray; +import com.wordplat.quickstart.json.JSONObject; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.HashMap; + +/** + * Author: wyouflf + * Time: 2014/05/26 + */ +public final class LoaderFactory { + private LoaderFactory() { + } + + /** + * key: loadType + */ + private static final HashMap converterHashMap = new HashMap(); + + static { + converterHashMap.put(JSONObject.class, new JSONObjectLoader()); + converterHashMap.put(JSONArray.class, new JSONArrayLoader()); + converterHashMap.put(String.class, new StringLoader()); + converterHashMap.put(File.class, new FileLoader()); + converterHashMap.put(byte[].class, new ByteArrayLoader()); + converterHashMap.put(InputStream.class, new InputStreamLoader()); + + BooleanLoader booleanLoader = new BooleanLoader(); + converterHashMap.put(boolean.class, booleanLoader); + converterHashMap.put(Boolean.class, booleanLoader); + + IntegerLoader integerLoader = new IntegerLoader(); + converterHashMap.put(int.class, integerLoader); + converterHashMap.put(Integer.class, integerLoader); + } + + /** + * getLoader + * + * @param type + * @return Loader + */ + public static Loader getLoader(Type type) { + Loader result = converterHashMap.get(type); + if (result == null) { + result = new ObjectLoader(type); + } else { + result = result.newInstance(); + } + return result; + } + + /** + * registerLoader + * + * @param type + * @param loader + * @param + */ + public static void registerLoader(Type type, Loader loader) { + converterHashMap.put(type, loader); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ObjectLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ObjectLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..2aabbae5eeb808e869f6bd3a83b739a52151354b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/ObjectLoader.java @@ -0,0 +1,111 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.common.util.ParameterizedTypeUtil; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.annotation.HttpResponse; +import com.wordplat.quickstart.xutils.http.app.ResponseParser; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; + +/** + * Created by lei.jiao on 2014/6/27. + * 其他对象的下载转换. + * 使用类型上的@HttpResponse注解信息进行数据转换. + * + * @since 2021-05-09 + */ +class ObjectLoader extends Loader { + private final Type objectType; + private final Class objectClass; + private final ResponseParser parser; + private final Loader innerLoader; + + /** + * ObjectLoader + * + * @param objectType + */ + public ObjectLoader(Type objectType) { + this.objectType = objectType; + + // check loadType & resultType + if (objectType instanceof ParameterizedType) { + objectClass = (Class) ((ParameterizedType) objectType).getRawType(); + } else if (objectType instanceof TypeVariable) { + throw new IllegalArgumentException( + "not support callback type " + objectType.toString()); + } else { + objectClass = (Class) objectType; + } + + HttpResponse response = null; + Type itemType = objectType; + if (List.class.equals(objectClass)) { + itemType = ParameterizedTypeUtil.getParameterizedType(this.objectType, List.class, 0); + Class itemClass = null; + if (itemType instanceof ParameterizedType) { + itemClass = (Class) ((ParameterizedType) itemType).getRawType(); + } else if (itemType instanceof TypeVariable) { + throw new IllegalArgumentException( + "not support callback type " + itemType.toString()); + } else { + itemClass = (Class) itemType; + } + + response = itemClass.getAnnotation(HttpResponse.class); + } else { + response = objectClass.getAnnotation(HttpResponse.class); + } + if (response != null) { + try { + Class parserCls = response.parser(); + this.parser = parserCls.newInstance(); + this.innerLoader = LoaderFactory.getLoader( + ParameterizedTypeUtil.getParameterizedType(parserCls, ResponseParser.class, 0)); + } catch (Throwable ex) { + throw new RuntimeException("create parser error", ex); + } + } else { + throw new IllegalArgumentException("not found @HttpResponse from " + itemType); + } + + if (innerLoader instanceof ObjectLoader) { + throw new IllegalArgumentException("not support callback type " + itemType); + } + } + + @Override + public Loader newInstance() { + throw new IllegalAccessError("use constructor create ObjectLoader."); + } + + @Override + public void setParams(final RequestParams params) { + this.innerLoader.setParams(params); + } + + @Override + @SuppressWarnings("unchecked") + public Object load(final UriRequest request) throws Throwable { + request.setResponseParser(parser); + Object innerLoaderResult = innerLoader.load(request); + return parser.parse(objectType, objectClass, innerLoaderResult); + } + + @Override + @SuppressWarnings("unchecked") + public Object loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + Object innerLoaderResult = innerLoader.loadFromCache(cacheEntity); + return parser.parse(objectType, objectClass, innerLoaderResult); + } + + @Override + public void save2Cache(UriRequest request) { + innerLoader.save2Cache(request); + } +} \ No newline at end of file diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/StringLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/StringLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..08372e7ea6cf7ea4be8b16d94668d3478c10b919 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/loader/StringLoader.java @@ -0,0 +1,54 @@ +package com.wordplat.quickstart.xutils.http.loader; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * + * @since 2021-05-09 + */ +class StringLoader extends Loader { + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new StringLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public String load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return resultStr; + } + + @Override + public String loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + return cacheEntity.getTextContent(); + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/AssetsRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/AssetsRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..8d21f5429a814d5f9888b662c50d9cea80803a4e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/AssetsRequest.java @@ -0,0 +1,42 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.http.RequestParams; + +import ohos.app.Context; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/11/4. + * Assets资源文件请求 + * + * @since 2021-05-09 + */ +public class AssetsRequest extends ResRequest { + /** + * AssetsRequest + * + * @param params + * @param loadType + * @throws Throwable + */ + public AssetsRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + Context context = params.getContext(); + String assetsPath = queryUrl.replace("assets://", ""); + /** + * todo + * inputStream = context.getResources().getAssets().open(assetsPath); + */ + contentLength = inputStream.available(); + } + return inputStream; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/HttpRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/HttpRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..07c280a17afaf28d3ea1f2ffa9488acec6465e1e --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/HttpRequest.java @@ -0,0 +1,446 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.KeyValue; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.ex.HttpException; +import com.wordplat.quickstart.xutils.http.HttpMethod; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.body.ProgressBody; +import com.wordplat.quickstart.xutils.http.body.RequestBody; +import com.wordplat.quickstart.xutils.http.cookie.DbCookieStore; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.net.*; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Created by wyouflf on 15/7/23. + * Uri请求发送和数据接收 + * + * @since 2021-05-09 + */ +public class HttpRequest extends UriRequest { + private static final CookieManager COOKIE_MANAGER = + new CookieManager(DbCookieStore.INSTANCE, CookiePolicy.ACCEPT_ALL); + private String cacheKey = null; + private boolean isLoading = false; + private InputStream inputStream = null; + private HttpURLConnection connection = null; + private int responseCode = 0; + + /** + * HttpRequest + * + * @param params + * @param loadType + * @throws Throwable + */ + public HttpRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + // build query + @Override + protected String buildQueryUrl(RequestParams params) throws IOException { + String uri = params.getUri(); + StringBuilder queryBuilder = new StringBuilder(uri); + if (!uri.contains("?")) { + queryBuilder.append("?"); + } else if (!uri.endsWith("?")) { + queryBuilder.append("&"); + } + List queryParams = params.getQueryStringParams(); + if (queryParams != null) { + for (KeyValue kv : queryParams) { + String name = kv.key; + String value = kv.getValueStrOrNull(); + if (!TextUtils.isEmpty(name) && value != null) { + queryBuilder.append(URLEncoder.encode(name, params.getCharset()).replaceAll("\\+", "%20")) + .append("=") + .append(URLEncoder.encode(value, params.getCharset()).replaceAll("\\+", "%20")) + .append("&"); + } + } + } + + if (queryBuilder.charAt(queryBuilder.length() - 1) == '&') { + queryBuilder.deleteCharAt(queryBuilder.length() - 1); + } + + if (queryBuilder.charAt(queryBuilder.length() - 1) == '?') { + queryBuilder.deleteCharAt(queryBuilder.length() - 1); + } + return queryBuilder.toString(); + } + + @Override + public String getRequestUri() { + String result = queryUrl; + if (connection != null) { + URL url = connection.getURL(); + if (url != null) { + result = url.toString(); + } + } + return result; + } + + @Override + public void sendRequest() throws Throwable { + isLoading = false; + responseCode = 0; + + URL url = new URL(queryUrl); + + Proxy proxy = params.getProxy(); + if (proxy != null) { + connection = (HttpURLConnection) url.openConnection(proxy); + } else { + connection = (HttpURLConnection) url.openConnection(); + } + connection.setReadTimeout(params.getReadTimeout()); + connection.setConnectTimeout(params.getConnectTimeout()); + connection.setInstanceFollowRedirects(params.getRedirectHandler() == null); + if (connection instanceof HttpsURLConnection) { + SSLSocketFactory sslSocketFactory = params.getSslSocketFactory(); + if (sslSocketFactory != null) { + ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory); + } + + HostnameVerifier hostnameVerifier = params.getHostnameVerifier(); + if (hostnameVerifier != null) { + ((HttpsURLConnection) connection).setHostnameVerifier(hostnameVerifier); + } + } + + if (params.isUseCookie()) { + try { + Map> singleMap = + COOKIE_MANAGER.get(url.toURI(), new HashMap>(0)); + List cookies = singleMap.get("Cookie"); + if (cookies != null) { + connection.setRequestProperty("Cookie", TextUtils.join(";", cookies)); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + List headers = params.getHeaders(); + if (headers != null) { + for (RequestParams.Header header : headers) { + String name = header.key; + String value = header.getValueStrOrNull(); + if (!TextUtils.isEmpty(name)) { + if (header.setHeader) { + connection.setRequestProperty(name, value); + } else { + connection.addRequestProperty(name, value); + } + } + } + } + if (responseParser != null) { + responseParser.beforeRequest(this); + } + if (requestInterceptListener != null) { + requestInterceptListener.beforeRequest(this); + } + + { // write body + HttpMethod method = params.getMethod(); + try { + connection.setRequestMethod(method.toString()); + } catch (ProtocolException ex) { + try { // fix: HttpURLConnection not support PATCH method. + Field methodField = HttpURLConnection.class.getDeclaredField("method"); + methodField.setAccessible(true); + methodField.set(connection, method.toString()); + } catch (Throwable ignored) { + throw ex; + } + } + if (HttpMethod.permitsRequestBody(method)) { + RequestBody body = params.getRequestBody(); + if (body != null) { + if (body instanceof ProgressBody) { + ((ProgressBody) body).setProgressHandler(progressHandler); + } + String contentType = body.getContentType(); + if (!TextUtils.isEmpty(contentType)) { + connection.setRequestProperty("Content-Type", contentType); + } + boolean isChunkedMode = false; + long contentLength = body.getContentLength(); + if (contentLength < 0) { + connection.setChunkedStreamingMode(256 * 1024); + isChunkedMode = true; + } else { + if (contentLength < Integer.MAX_VALUE) { + connection.setFixedLengthStreamingMode((int) contentLength); + } else { + connection.setChunkedStreamingMode(256 * 1024); + isChunkedMode = true; + } + } + + if (isChunkedMode) { + connection.setRequestProperty("Transfer-Encoding", "chunked"); + } else { + connection.setRequestProperty("Content-Length", String.valueOf(contentLength)); + } + + connection.setDoOutput(true); + body.writeTo(connection.getOutputStream()); + } + } + } + + responseCode = connection.getResponseCode(); + { + if (responseParser != null) { + responseParser.afterRequest(this); + } + if (requestInterceptListener != null) { + requestInterceptListener.afterRequest(this); + } + } + if (responseCode == 204 || responseCode == 205) { // empty content + throw new HttpException(responseCode, this.getResponseMessage()); + } else if (responseCode >= 300) { + HttpException httpException = new HttpException(responseCode, this.getResponseMessage()); + try { + httpException.setResult(IOUtil.readStr(this.getInputStream(), params.getCharset())); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + LogUtil.e(httpException.toString() + ", url: " + queryUrl); + throw httpException; + } + + isLoading = true; + } + + @Override + public boolean isLoading() { + return isLoading; + } + + @Override + public String getCacheKey() { + if (cacheKey == null) { + cacheKey = params.getCacheKey(); + + if (TextUtils.isEmpty(cacheKey)) { + cacheKey = params.toString(); + } + } + return cacheKey; + } + + @Override + public Object loadResult() throws Throwable { + isLoading = true; + return super.loadResult(); + } + + @Override + public Object loadResultFromCache() throws Throwable { + isLoading = true; + DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName()) + .setMaxSize(params.getCacheSize()) + .get(this.getCacheKey()); + + if (cacheEntity != null) { + if (HttpMethod.permitsCache(params.getMethod())) { + Date lastModified = cacheEntity.getLastModify(); + if (lastModified.getTime() > 0) { + params.setHeader("If-Modified-Since", toGMTString(lastModified)); + } + String eTag = cacheEntity.getEtag(); + if (!TextUtils.isEmpty(eTag)) { + params.setHeader("If-None-Match", eTag); + } + } + return loader.loadFromCache(cacheEntity); + } else { + return null; + } + } + + @Override + public void clearCacheHeader() { + params.setHeader("If-Modified-Since", null); + params.setHeader("If-None-Match", null); + } + + @Override + public InputStream getInputStream() throws IOException { + if (connection != null && inputStream == null) { + inputStream = connection.getResponseCode() >= 400 + ? connection.getErrorStream() : connection.getInputStream(); + } + return inputStream; + } + + @Override + public void close() throws IOException { + if (inputStream != null) { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + if (connection != null) { + connection.disconnect(); + /** + * connection = null; + */ + } + } + + @Override + public long getContentLength() { + long result = -1; + if (connection != null) { + try { + String value = connection.getHeaderField("content-length"); + if (value != null) { + result = Long.parseLong(value); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + if (result < 1) { + try { + result = this.getInputStream().available(); + } catch (Throwable ignored) { + String ss = ignored.toString(); + } + } + return result; + } + + @Override + public int getResponseCode() throws IOException { + if (connection != null) { + return responseCode; + } else { + if (this.getInputStream() != null) { + return 200; + } else { + return 404; + } + } + } + + @Override + public String getResponseMessage() throws IOException { + if (connection != null) { + return URLDecoder.decode(connection.getResponseMessage(), params.getCharset()); + } else { + return null; + } + } + + @Override + public long getExpiration() { + if (connection == null) { + return -1L; + } + + long expiration = -1L; + + String cacheControl = connection.getHeaderField("Cache-Control"); + if (!TextUtils.isEmpty(cacheControl)) { + StringTokenizer tok = new StringTokenizer(cacheControl, ","); + while (tok.hasMoreTokens()) { + String token = tok.nextToken().trim().toLowerCase(); + if (token.startsWith("max-age")) { + int eqIdx = token.indexOf('='); + if (eqIdx > 0) { + try { + String value = token.substring(eqIdx + 1).trim(); + long seconds = Long.parseLong(value); + if (seconds > 0L) { + expiration = System.currentTimeMillis() + seconds * 1000L; + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + break; + } + } + } + + // from expires + if (expiration <= 0L) { + expiration = connection.getExpiration(); + } + + if (expiration <= 0L && params.getCacheMaxAge() > 0L) { + expiration = System.currentTimeMillis() + params.getCacheMaxAge(); + } + + if (expiration <= 0L) { + expiration = Long.MAX_VALUE; + } + return expiration; + } + + @Override + public long getLastModified() { + return getHeaderFieldDate("Last-Modified", System.currentTimeMillis()); + } + + @Override + public String getETag() { + if (connection == null) { + return null; + } + return connection.getHeaderField("ETag"); + } + + @Override + public String getResponseHeader(String name) { + if (connection == null) { + return null; + } + return connection.getHeaderField(name); + } + + @Override + public Map> getResponseHeaders() { + if (connection == null) { + return null; + } + return connection.getHeaderFields(); + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + if (connection == null) { + return defaultValue; + } + return connection.getHeaderFieldDate(name, defaultValue); + } + + private static String toGMTString(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat( + "EEE, dd MMM y HH:mm:ss 'GMT'", Locale.US); + TimeZone gmtZone = TimeZone.getTimeZone("GMT"); + sdf.setTimeZone(gmtZone); + return sdf.format(date); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/LocalFileRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/LocalFileRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..48860cd8a18dd8742e8f7e05cec1052a946ce05b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/LocalFileRequest.java @@ -0,0 +1,141 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.loader.FileLoader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/11/4. + * 本地文件请求 + * + * @since 2021-05-09 + */ +public class LocalFileRequest extends UriRequest { + private InputStream inputStream; + + /** + * LocalFileRequest + * + * @param params + * @param loadType + * @throws Throwable + */ + public LocalFileRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public void sendRequest() throws Throwable { + + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public String getCacheKey() { + return queryUrl; + } + + @Override + public Object loadResult() throws Throwable { + if (loader instanceof FileLoader) { + return getFile(); + } + return this.loader.load(this); + } + + @Override + public Object loadResultFromCache() throws Throwable { + return null; + } + + @Override + public void clearCacheHeader() { + + } + + @Override + public void save2Cache() { + + } + + private File getFile() { + String filePath = null; + if (queryUrl.startsWith("file:")) { + filePath = queryUrl.substring("file:".length()); + } else { + filePath = queryUrl; + } + return new File(filePath); + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + inputStream = new FileInputStream(getFile()); + } + return inputStream; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + + @Override + public long getContentLength() { + return getFile().length(); + } + + @Override + public int getResponseCode() throws IOException { + return getFile().exists() ? 200 : 404; + } + + @Override + public String getResponseMessage() throws IOException { + return null; + } + + @Override + public long getExpiration() { + return -1; + } + + @Override + public long getLastModified() { + return getFile().lastModified(); + } + + @Override + public String getETag() { + return null; + } + + @Override + public String getResponseHeader(String name) { + return null; + } + + @Override + public Map> getResponseHeaders() { + return null; + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + return defaultValue; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/ResRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/ResRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..bcaa25b5e1f4e4d6c2fbf223b2c2551f888b5b38 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/ResRequest.java @@ -0,0 +1,184 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.x; + +import ohos.app.Context; +import ohos.bundle.ApplicationInfo; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/11/4. + * 本地资源请求 + * + * @since 2021-05-09 + */ +public class ResRequest extends UriRequest { + private static long lastModifiedTime = 0; + protected long contentLength = 0; + protected InputStream inputStream; + + /** + * ResRequest + * + * @param params + * @param loadType + * @throws Throwable + */ + public ResRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public void sendRequest() throws Throwable { + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public String getCacheKey() { + return queryUrl; + } + + @Override + public Object loadResult() throws Throwable { + return this.loader.load(this); + } + + @Override + public Object loadResultFromCache() throws Throwable { + DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName()) + .setMaxSize(params.getCacheSize()) + .get(this.getCacheKey()); + + if (cacheEntity != null) { + Date lastModifiedDate = cacheEntity.getLastModify(); + if (lastModifiedDate == null || lastModifiedDate.getTime() < getLastModified()) { + return null; + } + return loader.loadFromCache(cacheEntity); + } else { + return null; + } + } + + @Override + public void clearCacheHeader() { + } + + private int getResId() { + int resId = 0; + String resIdStr = queryUrl.substring("res:".length()); + resIdStr = resIdStr.replace("/", ""); + if (TextUtils.isDigitsOnly(resIdStr)) { + resId = Integer.parseInt(resIdStr); + } + + if (resId <= 0) { + throw new IllegalArgumentException("resId not found in url:" + queryUrl); + } + + return resId; + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + Context context = params.getContext(); + /** + * todo + * inputStream = context.getResources().openRawResource(getResId()); + */ + contentLength = inputStream.available(); + } + return inputStream; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + + @Override + public long getContentLength() { + try { + getInputStream(); + return contentLength; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return -1; + } + + @Override + public int getResponseCode() throws IOException { + return getInputStream() != null ? 200 : 404; + } + + @Override + public String getResponseMessage() throws IOException { + return null; + } + + @Override + public long getExpiration() { + return Long.MAX_VALUE; + } + + @Override + public long getLastModified() { + if (lastModifiedTime == 0) { + try { + Context context = params.getContext(); + File appFile = new File(x.app().getFilesDir().getPath()); + if (appFile.exists()) { + lastModifiedTime = appFile.lastModified(); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + lastModifiedTime = 0; + } finally { + if (lastModifiedTime == 0) { + lastModifiedTime = System.currentTimeMillis(); + } + } + } + return lastModifiedTime; + } + + @Override + public String getETag() { + return null; + } + + @Override + public String getResponseHeader(String name) { + return null; + } + + @Override + public Map> getResponseHeaders() { + return null; + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + return defaultValue; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequest.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..75be74ad85393b6c1d558690ab890755bebbc513 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequest.java @@ -0,0 +1,228 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.http.ProgressHandler; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.app.RequestInterceptListener; +import com.wordplat.quickstart.xutils.http.app.ResponseParser; +import com.wordplat.quickstart.xutils.http.loader.Loader; +import com.wordplat.quickstart.xutils.http.loader.LoaderFactory; +import com.wordplat.quickstart.xutils.x; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/7/23. + * Uri请求发送和数据接收 + * + * @since 2021-05-09 + */ +public abstract class UriRequest implements Closeable { + protected final String queryUrl; + protected final RequestParams params; + protected final Loader loader; + + protected ProgressHandler progressHandler = null; + protected ResponseParser responseParser = null; + protected RequestInterceptListener requestInterceptListener = null; + + /** + * UriRequest + * + * @param params + * @param loadType + * @throws Throwable + */ + public UriRequest(RequestParams params, Type loadType) throws Throwable { + this.params = params; + this.queryUrl = buildQueryUrl(params); + this.loader = LoaderFactory.getLoader(loadType); + this.loader.setParams(params); + } + + /** + * // build query + * + * @param params + * @return String + * @throws IOException + */ + protected String buildQueryUrl(RequestParams params) throws IOException { + return params.getUri(); + } + + /** + * setProgressHandler + * + * @param progressHandler + */ + public void setProgressHandler(ProgressHandler progressHandler) { + this.progressHandler = progressHandler; + this.loader.setProgressHandler(progressHandler); + } + + public void setResponseParser(ResponseParser responseParser) { + this.responseParser = responseParser; + } + + public void setRequestInterceptListener(RequestInterceptListener requestInterceptListener) { + this.requestInterceptListener = requestInterceptListener; + } + + public RequestParams getParams() { + return params; + } + + public String getRequestUri() { + return queryUrl; + } + + /** + * sendRequest + * + * @throws Throwable + */ + public abstract void sendRequest() throws Throwable; + + /** + * isLoading + * + * @return boolean + */ + public abstract boolean isLoading(); + + /** + * getCacheKey + * + * @return String + */ + public abstract String getCacheKey(); + + /** + * loadResult + * + * @return loadResult + * @throws Throwable + */ + public Object loadResult() throws Throwable { + return this.loader.load(this); + } + + /** + * loadResultFromCache + * + * @return Object + * @throws Throwable + */ + public abstract Object loadResultFromCache() throws Throwable; + + /** + * clearCacheHeader + */ + public abstract void clearCacheHeader(); + + /** + * save2Cache + */ + public void save2Cache() { + x.task().run(new Runnable() { + @Override + public void run() { + try { + loader.save2Cache(UriRequest.this); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + /** + * getInputStream + * + * @return InputStream + * @throws IOException + */ + public abstract InputStream getInputStream() throws IOException; + + @Override + public abstract void close() throws IOException; + + /** + * getContentLength + * + * @return long + */ + public abstract long getContentLength(); + + /** + * getResponseCode + * + * @return int + * @throws IOException + */ + public abstract int getResponseCode() throws IOException; + + /** + * getResponseMessage + * + * @return String + * @throws IOException + */ + public abstract String getResponseMessage() throws IOException; + + /** + * getExpiration + * + * @return long + */ + public abstract long getExpiration(); + + /** + * getLastModified + * + * @return long + */ + public abstract long getLastModified(); + + /** + * getETag + * + * @return String + */ + public abstract String getETag(); + + /** + * getResponseHeader + * + * @param name + * @return String + */ + public abstract String getResponseHeader(String name); + + /** + * getResponseHeaders + * + * @return Map + */ + public abstract Map> getResponseHeaders(); + + /** + * getHeaderFieldDate + * + * @param name + * @param defaultValue + * @return long + */ + public abstract long getHeaderFieldDate(String name, long defaultValue); + + @Override + public String toString() { + return getRequestUri(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequestFactory.java b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2cee02c4023343512bc9c8e4ac60095dbcbee145 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/http/request/UriRequestFactory.java @@ -0,0 +1,104 @@ +package com.wordplat.quickstart.xutils.http.request; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.http.app.RequestTracker; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Type; +import java.util.HashMap; + +/** + * Created by wyouflf on 15/11/4. + * Uri请求创建工厂 + * + * @since 2021-05-09 + */ +public final class UriRequestFactory { + private static Class defaultTrackerCls; + + private static final HashMap> + SCHEME_CLS_MAP = new HashMap>(); + + private UriRequestFactory() { + } + + /** + * getUriRequest + * + * @param params + * @param loadType + * @return UriRequest + * @throws Throwable + * @throws IllegalArgumentException + */ + public static UriRequest getUriRequest(RequestParams params, Type loadType) throws Throwable { + + String scheme = null; + String uri = params.getUri(); + int index = uri.indexOf(":"); + if (uri.startsWith("/")) { + scheme = "file"; + } else if (index > 0) { + scheme = uri.substring(0, index); + } + + if (!TextUtils.isEmpty(scheme)) { + scheme = scheme.toLowerCase(); + Class cls = SCHEME_CLS_MAP.get(scheme); + if (cls != null) { + Constructor constructor + = cls.getConstructor(RequestParams.class, Type.class); + return constructor.newInstance(params, loadType); + } else { + if (scheme.startsWith("http")) { + return new HttpRequest(params, loadType); + } else if (scheme.equals("assets")) { + return new AssetsRequest(params, loadType); + } else if (scheme.equals("file")) { + return new LocalFileRequest(params, loadType); + } else if (scheme.equals("res")) { + return new ResRequest(params, loadType); + } else { + throw new IllegalArgumentException("The url not be support: " + uri); + } + } + } else { + throw new IllegalArgumentException("The url not be support: " + uri); + } + } + + /** + * registerDefaultTrackerClass + * + * @param trackerCls + */ + public static void registerDefaultTrackerClass(Class trackerCls) { + UriRequestFactory.defaultTrackerCls = trackerCls; + } + + /** + * getDefaultTracker + * + * @return RequestTracker + */ + public static RequestTracker getDefaultTracker() { + try { + return defaultTrackerCls == null ? null : defaultTrackerCls.newInstance(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return null; + } + + /** + * registerRequestClass + * + * @param scheme + * @param uriRequestCls + */ + public static void registerRequestClass(String scheme, Class uriRequestCls) { + SCHEME_CLS_MAP.put(scheme, uriRequestCls); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageAnimationHelper.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageAnimationHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..0abf2bb13ce589458cea9fb0db2284e5307e83aa --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageAnimationHelper.java @@ -0,0 +1,52 @@ +package com.wordplat.quickstart.xutils.image; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import ohos.agp.animation.Animator; +import ohos.agp.components.Image; +import ohos.media.image.PixelMap; + +import java.lang.reflect.Method; + +/** + * Created by wyouflf on 15/10/13. + * ImageView Animation Helper + * + * @since 2021-05-09 + */ +public final class ImageAnimationHelper { + private static final Method cloneMethod; + + static { + Method method = null; + try { + method = Animator.class.getDeclaredMethod("clone"); + method.setAccessible(true); + } catch (Throwable ex) { + method = null; + LogUtil.w(ex.getMessage(), ex); + } + cloneMethod = method; + } + + private ImageAnimationHelper() { + } + + /** + * fadeInDisplay + * + * @param imageView + * @param drawable + */ + public static void fadeInDisplay(final Image imageView, PixelMap drawable) { + } + + /** + * animationDisplay + * + * @param imageView + * @param drawable + * @param animation + */ + public static void animationDisplay(Image imageView, PixelMap drawable, Animator animation) { + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageDecoder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..e00881a2330f0eb5bf2700fdf40b3a624c830b68 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageDecoder.java @@ -0,0 +1,231 @@ +package com.wordplat.quickstart.xutils.image; + +import com.wordplat.quickstart.xutils.cache.DiskCacheEntity; +import com.wordplat.quickstart.xutils.cache.DiskCacheFile; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.task.PriorityExecutor; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; + +import ohos.media.image.ImageSource; +import ohos.media.image.PixelMap; +import ohos.media.image.common.ColorSpace; + +import java.io.*; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/10/9. + * ImageDecoder for ImageLoader + */ +public final class ImageDecoder { + private static final int BITMAP_DECODE_MAX_WORKER; + private static final AtomicInteger bitmapDecodeWorker = new AtomicInteger(0); + private static final Object bitmapDecodeLock = new Object(); + + private static final Object gifDecodeLock = new Object(); + private static final byte[] GIF_HEADER = new byte[]{'G', 'I', 'F'}; + + private static final Executor THUMB_CACHE_EXECUTOR = new PriorityExecutor(1, true); + private static final LruDiskCache THUMB_CACHE = LruDiskCache.getDiskCache("xUtils_img_thumb"); + + static { + int cpuCount = Runtime.getRuntime().availableProcessors(); + BITMAP_DECODE_MAX_WORKER = cpuCount > 4 ? 2 : 1; + } + + private ImageDecoder() { + } + + static void clearCacheFiles() { + THUMB_CACHE.clearCacheFiles(); + } + + static PixelMap decodeFileWithLock(final File file, + final ImageOptions options, + final Callback.Cancelable cancelable) throws IOException { + if (file == null || !file.exists() || file.length() < 1) { + return null; + } + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + PixelMap result = null; + PixelMap bitmap = null; + boolean decodeStarted = false; + try { + synchronized (bitmapDecodeLock) { + while (bitmapDecodeWorker.get() >= BITMAP_DECODE_MAX_WORKER + && (cancelable == null || !cancelable.isCancelled())) { + try { + bitmapDecodeLock.wait(); + } catch (InterruptedException iex) { + throw new Callback.CancelledException("cancelled during decode image"); + } catch (Throwable ignored) { + String ss = ignored.toString(); + } + } + } + + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + decodeStarted = true; + bitmapDecodeWorker.incrementAndGet(); + if (options.isCompress()) { + bitmap = getThumbCache(file, options); + } + if (bitmap == null) { + bitmap = decodeBitmap(file, options, cancelable); + if (bitmap != null && options.isCompress()) { + final PixelMap finalBitmap = bitmap; + THUMB_CACHE_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + saveThumbCache(file, options, finalBitmap); + } + }); + } + } + } finally { + if (decodeStarted) { + bitmapDecodeWorker.decrementAndGet(); + } + synchronized (bitmapDecodeLock) { + bitmapDecodeLock.notifyAll(); + } + } + if (bitmap != null) { + result = bitmap; + } + + return result; + } + + /** + * isGif + * + * @param file + * @return boolean + */ + public static boolean isGif(File file) { + FileInputStream in = null; + try { + in = new FileInputStream(file); + byte[] header = IOUtil.readBytes(in, 0, 3); + return Arrays.equals(GIF_HEADER, header); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(in); + } + + return false; + } + + /** + * decodeBitmap + * + * @param file + * @param options + * @param cancelable + * @return PixelMap + * @throws IOException + */ + public static PixelMap decodeBitmap(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException { + ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions(); + ImageSource imageSource = ImageSource.create(file, sourceOptions); + ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); + decodingOptions.sampleSize = calculateSampleSize( + imageSource.getImageInfo().size.width, imageSource.getImageInfo().size.height, + options.getMaxWidth(), options.getMaxHeight()); + return imageSource.createPixelmap(decodingOptions); + } + + /** + * 计算压缩采样倍数 + * + * @param rawWidth 图片宽度 + * @param rawHeight 图片高度 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return 压缩采样倍数 + */ + public static int calculateSampleSize(final int rawWidth, final int rawHeight, + final int maxWidth, final int maxHeight) { + int sampleSize = 1; + + if (rawWidth > maxWidth || rawHeight > maxHeight) { + if (rawWidth > rawHeight) { + sampleSize = Math.round((float) rawHeight / (float) maxHeight); + } else { + sampleSize = Math.round((float) rawWidth / (float) maxWidth); + } + + if (sampleSize < 1) { + sampleSize = 1; + } + + final float totalPixels = rawWidth * rawHeight; + + final float maxTotalPixels = maxWidth * maxHeight * 2; + + while (totalPixels / (sampleSize * sampleSize) > maxTotalPixels) { + sampleSize++; + } + } + return sampleSize; + } + + private static void saveThumbCache(File file, ImageOptions options, PixelMap thumbBitmap) { + DiskCacheEntity entity = new DiskCacheEntity(); + try { + entity.setKey( + file.getCanonicalPath() + "@" + file.lastModified() + options.toString()); + } catch (IOException e) { + String ee = e.toString(); + } + DiskCacheFile cacheFile = null; + OutputStream out = null; + try { + cacheFile = THUMB_CACHE.createDiskCacheFile(entity); + if (cacheFile != null) { + out = new FileOutputStream(cacheFile); + out.flush(); + cacheFile = cacheFile.commit(); + } + } catch (Throwable ex) { + IOUtil.deleteFileOrDir(cacheFile); + LogUtil.w(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(cacheFile); + IOUtil.closeQuietly(out); + } + } + + private static PixelMap getThumbCache(File file, ImageOptions options) { + DiskCacheFile cacheFile = null; + try { + cacheFile = THUMB_CACHE.getDiskCacheFile( + file.getCanonicalPath() + "@" + file.lastModified() + options.toString()); + if (cacheFile != null && cacheFile.exists()) { + + ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions(); + ImageSource imageSource = ImageSource.create(file, sourceOptions); + ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions(); + decodingOptions.desiredColorSpace = ColorSpace.ADOBE_RGB_1998; + return imageSource.createPixelmap(decodingOptions); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(cacheFile); + } + return null; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageLoader.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..afad962a48a3836d29c98a583c69a1db48d4e7d8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageLoader.java @@ -0,0 +1,580 @@ +package com.wordplat.quickstart.xutils.image; + +import com.wordplat.quickstart.xutils.cache.LruCache; +import com.wordplat.quickstart.xutils.cache.LruDiskCache; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.common.task.Priority; +import com.wordplat.quickstart.xutils.common.task.PriorityExecutor; +import com.wordplat.quickstart.xutils.common.util.IOUtil; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.ex.FileLockedException; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.x; + +import ohos.agp.components.Image; +import ohos.agp.components.element.Element; +import ohos.app.Context; +import ohos.media.image.PixelMap; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by wyouflf on 15/10/9. + * 图片加载控制 + */ +final class ImageLoader implements + Callback.PrepareCallback, + Callback.CacheCallback, + Callback.ProgressCallback, + Callback.TypedCallback, + Callback.Cancelable { + private static final AtomicInteger hashCodeSeed = new AtomicInteger(0); + private static final Type loadType = File.class; + private static final HashMap FAKE_IMG_MAP = new HashMap(); + private static final AtomicLong SEQ_SEEK = new AtomicLong(0); + private static final String DISK_CACHE_DIR_NAME = "xUtils_img"; + private static final Executor EXECUTOR = new PriorityExecutor(10, false); + private static final int MEM_CACHE_MIN_SIZE = 1024 * 1024 * 4; // 4M + private static final LruCache MEM_CACHE = + new LruCache(MEM_CACHE_MIN_SIZE) { + private boolean deepClear = false; + + @Override + protected int sizeOf(MemCacheKey key, PixelMap value) { + if (value instanceof PixelMap) { + PixelMap bitmap = value; + return bitmap == null ? 0 : bitmap.getBaseDensity(); + } + return super.sizeOf(key, value); + } + + @Override + public void trimToSize(int maxSize) { + if (maxSize < 0) { + deepClear = true; + } + super.trimToSize(maxSize); + deepClear = false; + } + + @Override + protected void entryRemoved(boolean evicted, MemCacheKey key, PixelMap oldValue, PixelMap newValue) { + super.entryRemoved(evicted, key, oldValue, newValue); + if (evicted && deepClear && oldValue instanceof ReusableDrawable) { + ((ReusableDrawable) oldValue).setMemCacheKey(null); + } + } + }; + private MemCacheKey key; + private ImageOptions options; + private WeakReference viewRef; + private int fileLockedExceptionRetryCount = 0; + private boolean hasCache = false; + + private final long seq = SEQ_SEEK.incrementAndGet(); + + private volatile boolean stopped = false; + private volatile boolean cancelled = false; + private volatile boolean skipOnWaitingCallback = false; + private volatile boolean skipOnFinishedCallback = false; + private Callback.Cancelable httpCancelable; + private Callback.CommonCallback callback; + private Callback.PrepareCallback prepareCallback; + private Callback.CacheCallback cacheCallback; + private Callback.ProgressCallback progressCallback; + + static { + int memClass = 1024 * 1024 * 1024; + + // Use 1/8th of the available memory for this memory cache. + int cacheSize = 1024 * 1024 * memClass / 8; + if (cacheSize < MEM_CACHE_MIN_SIZE) { + cacheSize = MEM_CACHE_MIN_SIZE; + } + MEM_CACHE.resize(cacheSize); + } + + private ImageLoader() { + } + + static void clearMemCache() { + MEM_CACHE.evictAll(); + } + + static void clearCacheFiles() { + LruDiskCache.getDiskCache(DISK_CACHE_DIR_NAME).clearCacheFiles(); + } + + /** + * load from Network or DiskCache, invoke in any thread. + */ + static Cancelable doLoadDrawable(final String url, + final ImageOptions options, + final Callback.CommonCallback callback) { + if (TextUtils.isEmpty(url)) { + postArgsException(null, options, "url is null", callback); + return null; + } + + FakeImageView fakeImageView = new FakeImageView(); + return doBind(fakeImageView, url, options, 0, callback); + } + + /** + * load from Network or DiskCache, invoke in any thread. + */ + /*package*/ + static Cancelable doLoadFile(final String url, + final ImageOptions options, + final Callback.CacheCallback callback) { + if (TextUtils.isEmpty(url)) { + postArgsException(null, options, "url is null", callback); + return null; + } + + RequestParams params = createRequestParams(null, url, options); + return x.http().get(params, callback); + } + + /** + * load from Network or DiskCache, invoke in ui thread. + */ + static Cancelable doBind(final Image view, + final String url, + final ImageOptions options, + final int fileLockedExceptionRetryCount, + final Callback.CommonCallback callback) { + ImageOptions localOptions = options; + if (view == null) { + postArgsException(null, localOptions, "view is null", callback); + return null; + } + + if (TextUtils.isEmpty(url)) { + postArgsException(view, localOptions, "url is null", callback); + return null; + } + + if (localOptions == null) { + localOptions = ImageOptions.DEFAULT; + } + localOptions.optimizeMaxSize(view); + MemCacheKey key = new MemCacheKey(url, localOptions); + PixelMap memDrawable = null; + if (localOptions.isUseMemCache()) { + memDrawable = MEM_CACHE.get(key); + if (memDrawable instanceof PixelMap) { + PixelMap bitmap = memDrawable; + if (bitmap == null || bitmap.isReleased()) { + memDrawable = null; + } + } + } + if (memDrawable != null) { // has mem cache + boolean trustMemCache = false; + try { + if (callback instanceof ProgressCallback) { + try { + ((ProgressCallback) callback).onWaiting(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + if (callback instanceof CacheCallback) { + try { + // 是否信任内存缓存. onStart 之后再次调用 onCache 时, 入参是磁盘缓存. + trustMemCache = ((CacheCallback) callback).onCache(memDrawable); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } else { + trustMemCache = true; + } + + // hit mem cache + if (trustMemCache) { + view.setScaleMode(localOptions.getImageScaleType()); + view.setPixelMap(memDrawable); + if (callback != null) { + try { + callback.onSuccess(memDrawable); + } catch (Throwable ex) { + callback.onError(ex, true); + } + } + } else { + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + loader.skipOnWaitingCallback = true; + return loader.doLoadRequest(view, url, localOptions, callback); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + trustMemCache = false; + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + loader.skipOnWaitingCallback = true; + return loader.doLoadRequest(view, url, localOptions, callback); + } finally { + if (trustMemCache && callback != null) { + try { + callback.onFinished(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } else { + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + return loader.doLoadRequest(view, url, localOptions, callback); + } + return null; + } + + private Cancelable doLoadRequest(Image view, + String url, + ImageOptions options, + Callback.CommonCallback callback) { + this.viewRef = new WeakReference(view); + this.options = options; + this.key = new MemCacheKey(url, options); + this.callback = callback; + if (callback instanceof Callback.ProgressCallback) { + this.progressCallback = (Callback.ProgressCallback) callback; + } + if (callback instanceof Callback.PrepareCallback) { + this.prepareCallback = (Callback.PrepareCallback) callback; + } + if (callback instanceof Callback.CacheCallback) { + this.cacheCallback = (Callback.CacheCallback) callback; + } + RequestParams params = createRequestParams(view.getContext(), url, options); + if (view instanceof FakeImageView) { + synchronized (FAKE_IMG_MAP) { + FAKE_IMG_MAP.put(view.hashCode() + url, (FakeImageView) view); + } + } + return httpCancelable = x.http().get(params, this); + } + + @Override + public void cancel() { + stopped = true; + cancelled = true; + if (httpCancelable != null) { + httpCancelable.cancel(); + } + } + + @Override + public boolean isCancelled() { + return cancelled || !validView4Callback(false); + } + + @Override + public void onWaiting() { + if (!skipOnWaitingCallback && progressCallback != null) { + progressCallback.onWaiting(); + } + } + + @Override + public void onStarted() { + if (validView4Callback(true) && progressCallback != null) { + progressCallback.onStarted(); + } + } + + @Override + public void onLoading(long total, long current, boolean isDownloading) { + if (validView4Callback(true) && progressCallback != null) { + progressCallback.onLoading(total, current, isDownloading); + } + } + + @Override + public Type getLoadType() { + return loadType; + } + + @Override + public PixelMap prepare(File rawData) throws Throwable { + if (!validView4Callback(true)) { + return null; + } + + if (!rawData.exists()) { + throw new FileNotFoundException(rawData.getCanonicalPath()); + } + + try { + PixelMap result = null; + if (prepareCallback != null) { + result = prepareCallback.prepare(rawData); + } + if (result == null) { + result = ImageDecoder.decodeFileWithLock(rawData, options, this); + } + if (result != null) { + if (result instanceof ReusableDrawable) { + ((ReusableDrawable) result).setMemCacheKey(key); + MEM_CACHE.put(key, result); + } + } + return result; + } catch (IOException ex) { + IOUtil.deleteFileOrDir(rawData); + throw ex; + } + } + + @Override + public boolean onCache(PixelMap result) { + if (!validView4Callback(true)) { + return false; + } + + if (result != null) { + hasCache = true; + setSuccessDrawable4Callback(result); + if (cacheCallback != null) { + return cacheCallback.onCache(result); + } else if (callback != null) { + callback.onSuccess(result); + return true; + } + return true; + } + + return false; + } + + @Override + public void onSuccess(PixelMap result) { + if (!validView4Callback(!hasCache)) { + return; + } + + if (result != null) { + setSuccessDrawable4Callback(result); + if (callback != null) { + callback.onSuccess(result); + LogUtil.e("图片请求成功success==" + result); + viewRef.get().getContext().getUITaskDispatcher().asyncDispatch(new Runnable() { + @Override + public void run() { + viewRef.get().setPixelMap(result); + } + }); + } + } + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + stopped = true; + if (!validView4Callback(false)) { + return; + } + + fileLockedExceptionRetryCount++; + if (ex instanceof FileLockedException && fileLockedExceptionRetryCount < 1000/*max*/) { + LogUtil.d("ImageFileLocked: " + key.url); + x.task().postDelayed(new Runnable() { + @Override + public void run() { + Image imageView = viewRef.get(); + if (imageView != null) { + doBind(imageView, key.url, options, fileLockedExceptionRetryCount, callback); + } else { + ImageLoader.this.onFinished(); + } + } + }, 10); + skipOnFinishedCallback = true; + } else { + LogUtil.e(key.url, ex); + setErrorDrawable4Callback(); + if (callback != null) { + callback.onError(ex, isOnCallback); + } + } + } + + @Override + public void onCancelled(CancelledException cex) { + stopped = true; + if (!validView4Callback(false)) { + return; + } + if (callback != null) { + callback.onCancelled(cex); + } + } + + @Override + public void onFinished() { + stopped = true; + if (skipOnFinishedCallback) { + return; + } + + Image view = viewRef.get(); + if (view instanceof FakeImageView) { + synchronized (FAKE_IMG_MAP) { + FAKE_IMG_MAP.remove(view.hashCode() + key.url); + } + } + + if (callback != null) { + callback.onFinished(); + } + } + + private static RequestParams createRequestParams(Context context, String url, ImageOptions options) { + RequestParams params = new RequestParams(url); + if (context != null) { + params.setContext(context); + } + params.setCacheDirName(DISK_CACHE_DIR_NAME); + params.setConnectTimeout(1000 * 8); + params.setPriority(Priority.BG_LOW); + params.setExecutor(EXECUTOR); + params.setCancelFast(true); + params.setUseCookie(false); + if (options != null) { + ImageOptions.ParamsBuilder paramsBuilder = options.getParamsBuilder(); + if (paramsBuilder != null) { + params = paramsBuilder.buildParams(params, options); + } + } + return params; + } + + private boolean validView4Callback(boolean forceValidAsyncDrawable) { + final Image view = viewRef.get(); + if (view != null) { + PixelMap otherDrawable = view.getPixelMap(); + if (otherDrawable instanceof PixelMap) { + return true; + } else if (forceValidAsyncDrawable) { + this.cancel(); + return false; + } + return true; + } + return false; + } + + private void setSuccessDrawable4Callback(final PixelMap drawable) { + final Image view = viewRef.get(); + view.getContext().getUITaskDispatcher().asyncDispatch(new Runnable() { + @Override + public void run() { + if (view != null) { + view.setScaleMode(options.getImageScaleType()); + if (options.getAnimation() != null) { + ImageAnimationHelper.animationDisplay(view, drawable, options.getAnimation()); + } else if (options.isFadeIn()) { + ImageAnimationHelper.fadeInDisplay(view, drawable); + } else { + view.setPixelMap(drawable); + } + } + } + }); + } + + private void setErrorDrawable4Callback() { + final Image view = viewRef.get(); + view.getContext().getUITaskDispatcher().asyncDispatch(new Runnable() { + @Override + public void run() { + if (view != null) { + PixelMap drawable = (PixelMap) options.getFailureDrawable(view); + view.setScaleMode(options.getPlaceholderScaleType()); + view.setPixelMap(drawable); + } + } + }); + } + + private static void postArgsException( + final Image view, final ImageOptions options, + final String exMsg, final Callback.CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + x.app().getContext().getUITaskDispatcher().asyncDispatch(new Runnable() { + @Override + public void run() { + try { + if (callback instanceof ProgressCallback) { + ((ProgressCallback) callback).onWaiting(); + } + if (view != null && options != null) { + view.setScaleMode(options.getPlaceholderScaleType()); + view.setPixelMap(options.getFailureDrawable(view)); + } + if (callback != null) { + callback.onError(new IllegalArgumentException(exMsg), false); + } + } catch (Throwable ex) { + if (callback != null) { + try { + callback.onError(ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } finally { + if (callback != null) { + try { + callback.onFinished(); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + }); + } + + private static final class FakeImageView extends Image { + private final int hashCode; + private PixelMap drawable; + + + public FakeImageView() { + super(x.app()); + hashCode = hashCodeSeed.incrementAndGet(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public void setImageElement(Element element) { + } + + @Override + public Element getImageElement() { + return null; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageManagerImpl.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..304913f9455dbe11e4e3014d6fce19ae7c047487 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageManagerImpl.java @@ -0,0 +1,98 @@ +package com.wordplat.quickstart.xutils.image; + +import com.wordplat.quickstart.xutils.ImageManager; +import com.wordplat.quickstart.xutils.common.Callback; +import com.wordplat.quickstart.xutils.x; + +import ohos.agp.components.Image; +import ohos.media.image.PixelMap; + +import java.io.File; + +/** + * Created by wyouflf on 15/10/9. + * + * @since 2021-05-09 + */ +public final class ImageManagerImpl implements ImageManager { + private static final Object lock = new Object(); + private static volatile ImageManagerImpl instance; + + private ImageManagerImpl() { + } + + /** + * registerInstance + */ + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new ImageManagerImpl(); + } + } + } + x.Ext.setImageManager(instance); + } + + @Override + public void bind(final Image view, final String url) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, null, 0, null); + } + }); + } + + @Override + public void bind(final Image view, final String url, final ImageOptions options) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, options, 0, null); + } + }); + } + + @Override + public void bind(final Image view, final String url, final Callback.CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, null, 0, callback); + } + }); + } + + @Override + public void bind(final Image view, final String url, final ImageOptions options, final Callback.CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, options, 0, callback); + } + }); + } + + @Override + public Callback.Cancelable loadDrawable(String url, ImageOptions options, Callback.CommonCallback callback) { + return ImageLoader.doLoadDrawable(url, options, callback); + } + + @Override + public Callback.Cancelable loadFile(String url, ImageOptions options, Callback.CacheCallback callback) { + return ImageLoader.doLoadFile(url, options, callback); + } + + @Override + public void clearMemCache() { + ImageLoader.clearMemCache(); + } + + @Override + public void clearCacheFiles() { + ImageLoader.clearCacheFiles(); + ImageDecoder.clearCacheFiles(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageOptions.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..ebcb13195cb8163c642405790a04878292b70659 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ImageOptions.java @@ -0,0 +1,541 @@ +package com.wordplat.quickstart.xutils.image; + +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.http.RequestParams; +import com.wordplat.quickstart.xutils.x; + +import ohos.agp.animation.Animator; +import ohos.agp.components.Image; +import ohos.agp.window.service.DisplayManager; +import ohos.media.image.PixelMap; + +/** + * Created by wyouflf on 15/8/21. + * 图片加载参数 + * + * @since 2021-05-09 + */ +public class ImageOptions { + public static final ImageOptions DEFAULT = new ImageOptions(); + + // region ###################### decode options (equals & hashcode prop) ################ + private int maxWidth = 0; + private int maxHeight = 0; + /** + * // 小于0时不采样压缩. 等于0时自动识别ImageView的宽高和maxWidth. + */ + private int width = 0; + /** + * // 小于0时不采样压缩. 等于0时自动识别ImageView的宽高和maxHeight. + */ + private int height = 0; + private boolean crop = false; // crop to (width, height) + + private int radius = 0; + private boolean square = false; + private boolean circular = false; + private boolean autoRotate = false; + private boolean compress = true; + + private boolean ignoreGif = true; + private int gifRate = 100; + + private int loadingDrawableId = 0; + private int failureDrawableId = 0; + private PixelMap loadingDrawable = null; + private PixelMap failureDrawable = null; + private boolean forceLoadingDrawable = true; + + private Image.ScaleMode placeholderScaleType = Image.ScaleMode.INSIDE; + private Image.ScaleMode imageScaleType = Image.ScaleMode.CENTER; + + private boolean fadeIn = false; + private Animator animation = null; + + private boolean useMemCache = true; + private ParamsBuilder paramsBuilder; + + /** + * ImageOptions + */ + protected ImageOptions() { + } + + final void optimizeMaxSize(Image view) { + if (width > 0 && height > 0) { + maxWidth = width; + maxHeight = height; + return; + } + + int screenWidth = DisplayManager.getInstance().getDefaultDisplay(x.app()).get().getAttributes().width; + int screenHeight = DisplayManager.getInstance().getDefaultDisplay(x.app()).get().getAttributes().height; + + if (this == DEFAULT) { + maxWidth = width = screenWidth * 3 / 2; + maxHeight = height = screenHeight * 3 / 2; + return; + } + + if (width < 0) { + maxWidth = screenWidth * 3 / 2; + compress = false; + } + if (height < 0) { + maxHeight = screenHeight * 3 / 2; + compress = false; + } + + if (view == null && maxWidth <= 0 && maxHeight <= 0) { + maxWidth = screenWidth; + maxHeight = screenHeight; + } else { + int tempWidth = maxWidth; + int tempHeight = maxHeight; + + if (tempWidth <= 0) { + tempWidth = screenWidth; + } + if (tempHeight <= 0) { + tempHeight = screenHeight; + } + + maxWidth = tempWidth; + maxHeight = tempHeight; + } + } + + public int getMaxWidth() { + return maxWidth; + } + + public int getMaxHeight() { + return maxHeight; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public boolean isCrop() { + return crop; + } + + public int getRadius() { + return radius; + } + + public boolean isSquare() { + return square; + } + + public boolean isCircular() { + return circular; + } + + public boolean isIgnoreGif() { + return ignoreGif; + } + + public int getGifRate() { + return gifRate; + } + + public boolean isAutoRotate() { + return autoRotate; + } + + public boolean isCompress() { + return compress; + } + + public PixelMap getLoadingDrawable(Image view) { + if (loadingDrawable == null && loadingDrawableId > 0 && view != null) { + try { + view.setImageAndDecodeBounds(loadingDrawableId); + loadingDrawable = view.getPixelMap(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return loadingDrawable; + } + + /** + * getFailureDrawable + * + * @param view + * @return PixelMap + */ + public PixelMap getFailureDrawable(Image view) { + if (failureDrawable == null && failureDrawableId > 0 && view != null) { + try { + view.setImageAndDecodeBounds(failureDrawableId); + failureDrawable = view.getPixelMap(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return failureDrawable; + } + + public boolean isFadeIn() { + return fadeIn; + } + + public Animator getAnimation() { + return animation; + } + + public Image.ScaleMode getPlaceholderScaleType() { + return placeholderScaleType; + } + + public Image.ScaleMode getImageScaleType() { + return imageScaleType; + } + + public boolean isForceLoadingDrawable() { + return forceLoadingDrawable; + } + + public boolean isUseMemCache() { + return useMemCache; + } + + public ParamsBuilder getParamsBuilder() { + return paramsBuilder; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ImageOptions options = (ImageOptions) o; + + if (maxWidth != options.maxWidth) { + return false; + } + if (maxHeight != options.maxHeight) { + return false; + } + if (width != options.width) { + return false; + } + if (height != options.height) { + return false; + } + if (crop != options.crop) { + return false; + } + if (radius != options.radius) { + return false; + } + if (square != options.square) { + return false; + } + if (circular != options.circular) { + return false; + } + if (autoRotate != options.autoRotate) { + return false; + } + if (compress != options.compress) { + return false; + } + return false; + } + + @Override + public int hashCode() { + int result = maxWidth; + result = 31 * result + maxHeight; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + (crop ? 1 : 0); + result = 31 * result + radius; + result = 31 * result + (square ? 1 : 0); + result = 31 * result + (circular ? 1 : 0); + result = 31 * result + (autoRotate ? 1 : 0); + result = 31 * result + (compress ? 1 : 0); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("_"); + sb.append(maxWidth).append("_"); + sb.append(maxHeight).append("_"); + sb.append(width).append("_"); + sb.append(height).append("_"); + sb.append(radius).append("_"); + sb.append(crop ? 1 : 0).append(square ? 1 : 0).append(circular ? 1 : 0); + sb.append(autoRotate ? 1 : 0).append(compress ? 1 : 0); + return sb.toString(); + } + + /** + * ParamsBuilder + * + * @since 2021-05-09 + */ + public interface ParamsBuilder { + /** + * buildParams + * + * @param params + * @param options + * @return RequestParams + */ + RequestParams buildParams(RequestParams params, ImageOptions options); + } + + public static class Builder { + protected ImageOptions options; + + /** + * Builder + */ + public Builder() { + newImageOptions(); + } + + /** + * newImageOptions + */ + protected void newImageOptions() { + options = new ImageOptions(); + } + + /** + * build + * + * @return ImageOptions + */ + public ImageOptions build() { + return options; + } + + /** + * setSize + * + * @param width + * @param height + * @return Builder + */ + public Builder setSize(int width, int height) { + options.width = width; + options.height = height; + return this; + } + + /** + * setCrop + * + * @param crop + * @return Builder + */ + public Builder setCrop(boolean crop) { + options.crop = crop; + return this; + } + + /** + * setRadius + * + * @param radius + * @return Builder + */ + public Builder setRadius(int radius) { + options.radius = radius; + return this; + } + + /** + * setSquare + * + * @param square + * @return Builder + */ + public Builder setSquare(boolean square) { + options.square = square; + return this; + } + + /** + * setCircular + * + * @param circular + * @return Builder + */ + public Builder setCircular(boolean circular) { + options.circular = circular; + return this; + } + + /** + * setAutoRotate + * + * @param autoRotate + * @return Builder + */ + public Builder setAutoRotate(boolean autoRotate) { + options.autoRotate = autoRotate; + return this; + } + + + /** + * setIgnoreGif + * + * @param ignoreGif + * @return Builder + */ + public Builder setIgnoreGif(boolean ignoreGif) { + options.ignoreGif = ignoreGif; + return this; + } + + /** + * setGifRate + * + * @param rate + * @return Builder + */ + public Builder setGifRate(int rate) { + options.gifRate = rate; + return this; + } + + /** + * setLoadingDrawableId + * + * @param loadingDrawableId + * @return Builder + */ + public Builder setLoadingDrawableId(int loadingDrawableId) { + options.loadingDrawableId = loadingDrawableId; + return this; + } + + /** + * setLoadingDrawable + * + * @param loadingDrawable + * @return Builder + */ + public Builder setLoadingDrawable(PixelMap loadingDrawable) { + options.loadingDrawable = loadingDrawable; + return this; + } + + /** + * setFailureDrawableId + * + * @param failureDrawableId + * @return Builder + */ + public Builder setFailureDrawableId(int failureDrawableId) { + options.failureDrawableId = failureDrawableId; + return this; + } + + /** + * setFailureDrawable + * + * @param failureDrawable + * @return Builder + */ + public Builder setFailureDrawable(PixelMap failureDrawable) { + options.failureDrawable = failureDrawable; + return this; + } + + /** + * setFadeIn + * + * @param fadeIn + * @return Builder + */ + public Builder setFadeIn(boolean fadeIn) { + options.fadeIn = fadeIn; + return this; + } + + /** + * setAnimation + * + * @param animation + * @return Builder + */ + public Builder setAnimation(Animator animation) { + options.animation = animation; + return this; + } + + /** + * setPlaceholderScaleType + * + * @param placeholderScaleType + * @return Builder + */ + public Builder setPlaceholderScaleType(Image.ScaleMode placeholderScaleType) { + options.placeholderScaleType = placeholderScaleType; + return this; + } + + /** + * setImageScaleType + * + * @param imageScaleType + * @return Builder + */ + public Builder setImageScaleType(Image.ScaleMode imageScaleType) { + options.imageScaleType = imageScaleType; + return this; + } + + /** + * setForceLoadingDrawable + * + * @param forceLoadingDrawable + * @return Builder + */ + public Builder setForceLoadingDrawable(boolean forceLoadingDrawable) { + options.forceLoadingDrawable = forceLoadingDrawable; + return this; + } + + /** + * setUseMemCache + * + * @param useMemCache + * @return Builder + */ + public Builder setUseMemCache(boolean useMemCache) { + options.useMemCache = useMemCache; + return this; + } + + /** + * setParamsBuilder + * + * @param paramsBuilder + * @return Builder + */ + public Builder setParamsBuilder(ParamsBuilder paramsBuilder) { + options.paramsBuilder = paramsBuilder; + return this; + } + } + +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/MemCacheKey.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/MemCacheKey.java new file mode 100644 index 0000000000000000000000000000000000000000..4218397bab51c889596add22cce4d952be5cafaf --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/MemCacheKey.java @@ -0,0 +1,49 @@ +package com.wordplat.quickstart.xutils.image; + +/** + * Created by wyouflf on 15/10/20. + */ +final class MemCacheKey { + public final String url; + public final ImageOptions options; + + /** + * MemCacheKey + * + * @param url + * @param options + */ + public MemCacheKey(String url, ImageOptions options) { + this.url = url; + this.options = options; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MemCacheKey that = (MemCacheKey) o; + + if (!url.equals(that.url)) { + return false; + } + return options.equals(that.options); + } + + @Override + public int hashCode() { + int result = url.hashCode(); + result = 31 * result + options.hashCode(); + return result; + } + + @Override + public String toString() { + return url + options.toString(); + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableBitmapDrawable.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableBitmapDrawable.java new file mode 100644 index 0000000000000000000000000000000000000000..51919689716b9185880f13b8386807b19390666f --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableBitmapDrawable.java @@ -0,0 +1,21 @@ +package com.wordplat.quickstart.xutils.image; + +import ohos.media.image.PixelMap; + +final class ReusableBitmapDrawable extends PixelMap implements ReusableDrawable { + private MemCacheKey key; + + protected ReusableBitmapDrawable(long nativeImagePixelMap, long nativeAllocBytes) { + super(nativeImagePixelMap, nativeAllocBytes); + } + + @Override + public MemCacheKey getMemCacheKey() { + return key; + } + + @Override + public void setMemCacheKey(MemCacheKey key) { + this.key = key; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableDrawable.java b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableDrawable.java new file mode 100644 index 0000000000000000000000000000000000000000..8ce1a9a351529c21553def21c9414bb2cbaef213 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/image/ReusableDrawable.java @@ -0,0 +1,11 @@ +package com.wordplat.quickstart.xutils.image; + +/** + * Created by wyouflf on 15/10/20. + * 使已被LruCache移除, 但还在被ImageView使用的Drawable可以再次被回收使用. + */ +interface ReusableDrawable { + MemCacheKey getMemCacheKey(); + + void setMemCacheKey(MemCacheKey key); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/EventListenerManager.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/EventListenerManager.java new file mode 100644 index 0000000000000000000000000000000000000000..6ab54a8513b25ecb225536e7e448ee7e175a8cde --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/EventListenerManager.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.view; + +import com.wordplat.quickstart.xutils.common.util.DoubleKeyValueMap; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.common.util.TextUtils; +import com.wordplat.quickstart.xutils.view.annotation.Event; + +import ohos.agp.components.Component; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +final class EventListenerManager { + private static final DoubleKeyValueMap, Object> + listenerCache = new DoubleKeyValueMap, Object>(); + + private static final long QUICK_EVENT_TIME_SPAN = 300; + private static final HashSet AVOID_QUICK_EVENT_SET = new HashSet(2); + + static { + AVOID_QUICK_EVENT_SET.add("onClick"); + AVOID_QUICK_EVENT_SET.add("onItemClick"); + } + + private EventListenerManager() { + } + + /** + * k1: viewInjectInfo + * k2: interface Type + * value: listener + */ + public static void addEventMethod( + ViewFinder finder, + ViewInfo info, + Event event, + Object handler, + Method method) { + try { + Component view = finder.findViewByInfo(info); + + if (view != null) { + Class listenerType = event.type(); + String listenerSetter = event.setter(); + if (TextUtils.isEmpty(listenerSetter)) { + listenerSetter = "set" + listenerType.getSimpleName(); + } + + String methodName = event.method(); + + boolean addNewMethod = false; + Object listener = listenerCache.get(info, listenerType); + DynamicHandler dynamicHandler = null; + if (listener != null) { + dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener); + addNewMethod = handler.equals(dynamicHandler.getHandler()); + if (addNewMethod) { + dynamicHandler.addMethod(methodName, method); + } + } + + if (!addNewMethod) { + dynamicHandler = new DynamicHandler(handler); + + dynamicHandler.addMethod(methodName, method); + + listener = Proxy.newProxyInstance( + listenerType.getClassLoader(), + new Class[]{listenerType}, + dynamicHandler); + + listenerCache.put(info, listenerType, listener); + } + + Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); + setEventListenerMethod.invoke(view, listener); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + public static class DynamicHandler implements InvocationHandler { + private static long lastClickTime = 0; + private WeakReference handlerRef; + private final HashMap methodMap = new HashMap(1); + + private DynamicHandler(Object handler) { + this.handlerRef = new WeakReference(handler); + } + + public void addMethod(String name, Method method) { + methodMap.put(name, method); + } + + public Object getHandler() { + return handlerRef.get(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object handler = handlerRef.get(); + if (handler != null) { + String eventMethod = method.getName(); + if ("toString".equals(eventMethod)) { + return DynamicHandler.class.getSimpleName(); + } + + method = methodMap.get(eventMethod); + if (method == null && methodMap.size() == 1) { + for (Map.Entry entry : methodMap.entrySet()) { + if (TextUtils.isEmpty(entry.getKey())) { + method = entry.getValue(); + } + break; + } + } + + if (method != null) { + if (AVOID_QUICK_EVENT_SET.contains(eventMethod)) { + long timeSpan = System.currentTimeMillis() - lastClickTime; + if (timeSpan > 0 && timeSpan < QUICK_EVENT_TIME_SPAN) { + LogUtil.d("onClick cancelled: " + timeSpan); + return null; + } + lastClickTime = System.currentTimeMillis(); + } + + try { + return method.invoke(handler, args); + } catch (Throwable ex) { + throw new RuntimeException("invoke method error:" + + handler.getClass().getName() + "#" + method.getName(), ex); + } + } else { + LogUtil.w("method not impl: " + eventMethod + "(" + handler.getClass().getSimpleName() + ")"); + } + } + return null; + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewFinder.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..3853e936f6a4341deae02322966d2976239684a6 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewFinder.java @@ -0,0 +1,53 @@ +package com.wordplat.quickstart.xutils.view; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.Component; + +/** + * Author: wyouflf + * Date: 13-9-9 + * Time: 下午12:29 + * + * @since 2021-05-09 + */ +final class ViewFinder { + private Component view; + private AbilitySlice activity; + + public ViewFinder(Component view) { + this.view = view; + } + + public ViewFinder(AbilitySlice activity) { + this.activity = activity; + } + + public Component findViewById(int id) { + if (view != null) { + return view.findComponentById(id); + } + if (activity != null) { + return activity.findComponentById(id); + } + return null; + } + + public Component findViewByInfo(ViewInfo info) { + return findViewById(info.value, info.parentId); + } + + public Component findViewById(int id, int pid) { + Component pView = null; + if (pid > 0) { + pView = this.findViewById(pid); + } + + Component view = null; + if (pView != null) { + view = pView.findComponentById(id); + } else { + view = this.findViewById(id); + } + return view; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInfo.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..fe2a903f219ef49f4d68915856240778025181c5 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInfo.java @@ -0,0 +1,37 @@ +package com.wordplat.quickstart.xutils.view; + +/** + * Author: wyouflf + * Date: 13-12-5 + * Time: 下午11:25 + * + * @since 2021-05-09 + */ +final class ViewInfo { + public int value; + public int parentId; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ViewInfo viewInfo = (ViewInfo) o; + + if (value != viewInfo.value) { + return false; + } + return parentId == viewInfo.parentId; + } + + @Override + public int hashCode() { + int result = value; + result = 31 * result + parentId; + return result; + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInjectorImpl.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInjectorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..3ca88ca32f372954f44762658fa03fcbb91d6503 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/ViewInjectorImpl.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.view; + +import com.wordplat.quickstart.xutils.ViewInjector; +import com.wordplat.quickstart.xutils.common.util.LogUtil; +import com.wordplat.quickstart.xutils.view.annotation.ContentView; +import com.wordplat.quickstart.xutils.view.annotation.Event; +import com.wordplat.quickstart.xutils.view.annotation.ViewInject; +import com.wordplat.quickstart.xutils.x; +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.Component; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; + +/** + * ViewInjectorImpl + * + * @since 2021-05-09 + */ +public final class ViewInjectorImpl implements ViewInjector { + private static final HashSet> IGNORED = new HashSet>(); + + static { + IGNORED.add(Object.class); + IGNORED.add(AbilitySlice.class); + } + + private static final Object lock = new Object(); + private static volatile ViewInjectorImpl instance; + + private ViewInjectorImpl() { + } + + /** + * registerInstance + */ + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new ViewInjectorImpl(); + } + } + } + x.Ext.setViewInjector(instance); + } + + @Override + public void inject(Component view) { + injectObject(view, view.getClass(), new ViewFinder(view)); + } + + @Override + public void inject(AbilitySlice activity) { + /** + * 获取Activity的ContentView的注解 + */ + Class handlerType = activity.getClass(); + try { + ContentView contentView = findContentView(handlerType); + if (contentView != null) { + int viewId = contentView.value(); + if (viewId > 0) { + activity.setUIContent(viewId); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + injectObject(activity, handlerType, new ViewFinder(activity)); + } + + @Override + public void inject(Object handler, Component view) { + injectObject(handler, handler.getClass(), new ViewFinder(view)); + } + + private static ContentView findContentView(Class thisCls) { + ContentView contentView = thisCls.getAnnotation(ContentView.class); + if (contentView == null) { + return findContentView(thisCls.getSuperclass()); + } + return contentView; + } + + /** + * injectObject + * @param handler + * @param handlerType + * @param finder + */ + private static void injectObject(Object handler, Class handlerType, ViewFinder finder) { + + // 从父类到子类递归 + injectObject(handler, handlerType.getSuperclass(), finder); + + // inject view + Field[] fields = handlerType.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + Class fieldType = field.getType(); + if (Modifier.isStatic(field.getModifiers()) || + Modifier.isFinal(field.getModifiers()) || + fieldType.isPrimitive() || + fieldType.isArray()) { + continue; + } + + ViewInject viewInject = field.getAnnotation(ViewInject.class); + if (viewInject != null) { + try { + Component view = finder.findViewById(viewInject.value(), viewInject.parentId()); + if (view != null) { + field.setAccessible(true); + field.set(handler, view); + } else { + throw new RuntimeException("Invalid @ViewInject for " + + handlerType.getSimpleName() + "." + field.getName()); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + Method[] methods = handlerType.getDeclaredMethods(); + if (methods != null && methods.length > 0) { + for (Method method : methods) { + if (Modifier.isStatic(method.getModifiers()) + || !Modifier.isPrivate(method.getModifiers())) { + continue; + } + Event event = method.getAnnotation(Event.class); + if (event != null) { + try { + int[] values = event.value(); + int[] parentIds = event.parentId(); + int parentIdsLen = parentIds == null ? 0 : parentIds.length; + for (int i = 0; i < values.length; i++) { + int value = values[i]; + if (value > 0) { + ViewInfo info = new ViewInfo(); + info.value = value; + info.parentId = parentIdsLen > i ? parentIds[i] : 0; + method.setAccessible(true); + EventListenerManager.addEventMethod(finder, info, event, handler, method); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ContentView.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ContentView.java new file mode 100644 index 0000000000000000000000000000000000000000..04402b80a6d65acaecf4be858df7558e1e245371 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ContentView.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.view.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ContentView + * + * @since 2021-05-09 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContentView { + /** + * value + * + * @return int + */ + int value(); +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/Event.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..230184d93b321b76ab69fb56b3a7bc8eec8d73b8 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/Event.java @@ -0,0 +1,57 @@ +package com.wordplat.quickstart.xutils.view.annotation; + +import ohos.agp.components.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 事件注解. + * 被注解的方法必须具备以下形式: + * 1. private 修饰 + * 2. 返回值类型没有要求 + * 3. 参数签名和type的接口要求的参数签名一致. + * Author: wyouflf + * Date: 13-9-9 + * Time: 下午12:43 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Event { + /** + * 控件的id集合, id小于1时不执行ui事件绑定. + * + * @return int[] + */ + int[] value(); + + /** + * 控件的parent控件的id集合, 组合为(value[i], parentId[i] or 0). + * + * @return int[] + */ + int[] parentId() default 0; + + /** + * 事件的listener, 默认为点击事件. + * + * @return Class + */ + Class type() default Component.ClickedListener.class; + + /** + * 事件的setter方法名, 默认为set+type#simpleName. + * + * @return String + */ + String setter() default ""; + + /** + * 如果type的接口类型提供多个方法, 需要使用此参数指定方法名. + * + * @return String + */ + String method() default ""; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ViewInject.java b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ViewInject.java new file mode 100644 index 0000000000000000000000000000000000000000..bae27baf381c83f350473fe5fcd2b95143e1823b --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/view/annotation/ViewInject.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.quickstart.xutils.view.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * ViewInject + * + * @since 2021-05-09 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ViewInject { + /** + * value + * + * @return int + */ + int value(); + + /** + * parentId + * + * @return int + */ + int parentId() default 0; +} diff --git a/entry/src/main/java/com/wordplat/quickstart/xutils/x.java b/entry/src/main/java/com/wordplat/quickstart/xutils/x.java new file mode 100644 index 0000000000000000000000000000000000000000..b18e4b86bc032a97643454e61008e1c996c962d5 --- /dev/null +++ b/entry/src/main/java/com/wordplat/quickstart/xutils/x.java @@ -0,0 +1,160 @@ +package com.wordplat.quickstart.xutils; + +import com.wordplat.quickstart.xutils.common.TaskController; +import com.wordplat.quickstart.xutils.common.task.TaskControllerImpl; +import com.wordplat.quickstart.xutils.db.DbManagerImpl; +import com.wordplat.quickstart.xutils.ex.DbException; +import com.wordplat.quickstart.xutils.http.HttpManagerImpl; +import com.wordplat.quickstart.xutils.image.ImageManagerImpl; +import com.wordplat.quickstart.xutils.view.ViewInjectorImpl; + +import ohos.aafwk.ability.Ability; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; + +/** + * Created by wyouflf on 15/6/10. + * 任务控制中心, http, image, db, view注入等接口的入口. + * 需要在在application的onCreate中初始化: x.Ext.init(this); + * + * @since 2021-05-09 + */ +public final class x { + private x() { + } + + public static boolean isDebug() { + return Ext.debug; + } + + /** + * app + * + * @return Ability + */ + public static Ability app() { + return Ext.app; + } + + /** + * task + * + * @return TaskController + */ + public static TaskController task() { + return Ext.taskController; + } + + /** + * http + * + * @return HttpManager + */ + public static HttpManager http() { + if (Ext.httpManager == null) { + HttpManagerImpl.registerInstance(); + } + return Ext.httpManager; + } + + /** + * image + * + * @return ImageManager + */ + public static ImageManager image() { + if (Ext.imageManager == null) { + ImageManagerImpl.registerInstance(); + } + return Ext.imageManager; + } + + /** + * view + * + * @return ViewInjector + */ + public static ViewInjector view() { + if (Ext.viewInjector == null) { + ViewInjectorImpl.registerInstance(); + } + return Ext.viewInjector; + } + + /** + * getDb + * + * @param daoConfig + * @return DbManager + * @throws DbException + */ + public static DbManager getDb(DbManager.DaoConfig daoConfig) throws DbException { + return DbManagerImpl.getInstance(daoConfig); + } + + /** + * Ext + * + * @since 2021-05-09 + */ + public static class Ext { + private static boolean debug; + private static Ability app; + private static TaskController taskController; + private static HttpManager httpManager; + private static ImageManager imageManager; + private static ViewInjector viewInjector; + + private Ext() { + } + + /** + * init + * + * @param ability + */ + public static void init(Ability ability) { + TaskControllerImpl.registerInstance(); + if (Ext.app == null) { + Ext.app = ability; + } + } + + public static void setDebug(boolean debug) { + Ext.debug = debug; + } + + /** + * setTaskController + * + * @param taskController + */ + public static void setTaskController(TaskController taskController) { + if (Ext.taskController == null) { + Ext.taskController = taskController; + } + } + + public static void setHttpManager(HttpManager httpManager) { + Ext.httpManager = httpManager; + } + + public static void setImageManager(ImageManager imageManager) { + Ext.imageManager = imageManager; + } + + public static void setViewInjector(ViewInjector viewInjector) { + Ext.viewInjector = viewInjector; + } + + /** + * setDefaultHostnameVerifier + * + * @param hostnameVerifier + */ + public static void setDefaultHostnameVerifier(HostnameVerifier hostnameVerifier) { + HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); + } + } +} diff --git a/entry/src/main/resources/base/animation/loading_rotation.xml b/entry/src/main/resources/base/animation/loading_rotation.xml new file mode 100644 index 0000000000000000000000000000000000000000..dfdf3b5176b890ccc77529667e2a9c84c3f45a9d --- /dev/null +++ b/entry/src/main/resources/base/animation/loading_rotation.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3232b02d3362198a805145135f522868eee5156b --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,201 @@ +{ + "color": [ + { + "name": "ic_advertise", + "value": "#FFFFFF" + }, + { + "name": "colorCg", + "value": "#d3d7d4" + }, + { + "name": "layer_grid_line", + "value": "#000000" + }, + { + "name": "app_mainclolr", + "value": "#FB6A8F" + },{ + "name": "app_qing2", + "value": "#31C453" + },{ + "name": "color_1", + "value": "#AAA3DB" + },{ + "name": "color_2", + "value": "#86ACE9" + },{ + "name": "color_4", + "value": "#80D8A3" + },{ + "name": "color_5", + "value": "#F1C672" + },{ + "name": "color_6", + "value": "#FDAD8B" + },{ + "name": "color_7", + "value": "#ADBEFF" + },{ + "name": "color_8", + "value": "#94D6FA" + },{ + "name": "color_9", + "value": "#C3B5F6" + },{ + "name": "color_10", + "value": "#99CCFF" + },{ + "name": "color_11", + "value": "#FBA6ED" + },{ + "name": "color_30", + "value": "#EE8262" + },{ + "name": "bg_color", + "value":"#F0F0F0" + },{ + "name": "color_31", + "value": "#EE6363" + },{ + "name": "color_32", + "value": "#EEB4B4" + },{ + "name": "color_33", + "value": "#D2B48C" + },{ + "name": "color_34", + "value": "#CD9B9B" + },{ + "name": "color_35", + "value": "#5F9EA0" + }, + { + "name": "app_course_chooseweek_bg", + "value": "#E2F7F6" + }, + { + "name": "app_white", + "value": "#ffffff" + }, + { + "name": "app_course_chooseweek_bg2", + "value": "#F1FFFE" + }, + { + "name":"test", + "value":"#3FCAB8" + }, + { + "name":"app_red", + "value":"#d81e06" + }, + { + "name":"color_3", + "value":"#92D261" + }, + { + "name":"app_white_slight", + "value":"#FBFBFB" + }, + { + "name":"app_course_textcolor_blue", + "value":"#13B0F1" + }, + { + "name":"app_gray", + "value":"#999999" + }, + { + "name":"app_gold", + "value":"#FF7A59" + }, + { + "name": "colorAccent", + "value": "#FF4081" + }, + { + "name": "colorPrimary", + "value": "#dc7e2c" + }, + { + "name": "colorSecondary", + "value": "#99000000" + }, + { + "name": "colorAppbarTitle", + "value": "#000000" + }, + { + "name": "colorAppbarBg", + "value": "#ffffff" + }, + { + "name": "colorAppbarSubBg", + "value": "#ffffff" + }, + { + "name": "textColorPrimary", + "value": "#E6000000" + }, + { + "name": "textColorSecondary", + "value": "#99000000" + }, + { + "name": "colorCardViewBg", + "value": "#ffffff" + }, + { + "name": "colorAppBackground", + "value": "#f1f3f5" + }, + { + "name": "colorSearchBarBackground", + "value": "#0C000000" + }, + { + "name": "colorListDivider", + "value": "#33000000" + }, + { + "name": "divider", + "value": "#dddddd" + }, + { + "name": "colorListHeadBackground", + "value": "#77787b" + }, + { + "name": "white", + "value": "#ffffff" + }, + { + "name": "colorPrimaryDark", + "value": "#1e82d2" + }, + { + "name": "black", + "value": "#000000" + }, + { + "name": "green", + "value": "#45b97c" + }, + { + "name": "brown", + "value": "#843900" + },{ + "name":"bb_inActiveBottomBarItemColor", + "value":"#747474" + }, + { + "name":"bb_darkBackgroundColor", + "value":"#212121" + }, + { + "name":"bb_tabletRightBorderDark", + "value":"#505050" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..771787272c2a355a6b842737a4cf5efb33217ebb --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "app_name", + "value": "ikvStockChart" + }, + { + "name": "mainability_description", + "value": "Java_Phone_Empty Feature Ability" + }, + { + "name": "HelloWorld", + "value": "Hello World" + }, + { + "name": "Warning_Unknow", + "value": "程序发生了错误" + }, + { + "name": "Warning_No_Network", + "value": "当前网络不给力,请检查您的网络设置" + }, + { + "name": "Warning_Network_Timeout", + "value": "当前网络不给力,请稍后重试" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/background_ability_main.xml b/entry/src/main/resources/base/graphic/background_ability_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..015ce26061cae0eb59b0e3bf54d2c5ba994267e4 --- /dev/null +++ b/entry/src/main/resources/base/graphic/background_ability_main.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/background_change.xml b/entry/src/main/resources/base/graphic/background_change.xml new file mode 100644 index 0000000000000000000000000000000000000000..96280bcb2a395756d2854f5051bbac54bde474c9 --- /dev/null +++ b/entry/src/main/resources/base/graphic/background_change.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/background_shape.xml b/entry/src/main/resources/base/graphic/background_shape.xml new file mode 100644 index 0000000000000000000000000000000000000000..2840d4b71e4a30837f15b2e339d3dc05f2a5f749 --- /dev/null +++ b/entry/src/main/resources/base/graphic/background_shape.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/toast_background_shape.xml b/entry/src/main/resources/base/graphic/toast_background_shape.xml new file mode 100644 index 0000000000000000000000000000000000000000..39c4272457408e069dac66e9533cfe322c846191 --- /dev/null +++ b/entry/src/main/resources/base/graphic/toast_background_shape.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/vector_drawable_loading2.xml b/entry/src/main/resources/base/graphic/vector_drawable_loading2.xml new file mode 100644 index 0000000000000000000000000000000000000000..e7d9ee77c65f5429b87abe67dca9b428ff204c85 --- /dev/null +++ b/entry/src/main/resources/base/graphic/vector_drawable_loading2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/vector_drawable_loading2_anim.xml b/entry/src/main/resources/base/graphic/vector_drawable_loading2_anim.xml new file mode 100644 index 0000000000000000000000000000000000000000..21b52f10a69ee035b7fac9993c7c6bcb03b6e9d8 --- /dev/null +++ b/entry/src/main/resources/base/graphic/vector_drawable_loading2_anim.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/graphic/vector_drawable_loading_anim.xml b/entry/src/main/resources/base/graphic/vector_drawable_loading_anim.xml new file mode 100644 index 0000000000000000000000000000000000000000..21b52f10a69ee035b7fac9993c7c6bcb03b6e9d8 --- /dev/null +++ b/entry/src/main/resources/base/graphic/vector_drawable_loading_anim.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/Header.xml b/entry/src/main/resources/base/layout/Header.xml new file mode 100644 index 0000000000000000000000000000000000000000..cef832f29af29699efbdef257f94a57189b6893a --- /dev/null +++ b/entry/src/main/resources/base/layout/Header.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/head_default_layout.xml b/entry/src/main/resources/base/layout/head_default_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..740ed943644ba87419ba02f8a6c1ae4a5082a3a9 --- /dev/null +++ b/entry/src/main/resources/base/layout/head_default_layout.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/item_text.xml b/entry/src/main/resources/base/layout/item_text.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec37720d1ce13e0087ba917321b233ff2f12fdad --- /dev/null +++ b/entry/src/main/resources/base/layout/item_text.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slice_left_and_right_refresh.xml b/entry/src/main/resources/base/layout/slice_left_and_right_refresh.xml new file mode 100644 index 0000000000000000000000000000000000000000..5d3146b89e3da074529872bec5d407ad836499c7 --- /dev/null +++ b/entry/src/main/resources/base/layout/slice_left_and_right_refresh.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_macd_rsi_kdj_show_together.xml b/entry/src/main/resources/base/layout/slice_macd_rsi_kdj_show_together.xml similarity index 37% rename from app/src/main/res/layout/activity_macd_rsi_kdj_show_together.xml rename to entry/src/main/resources/base/layout/slice_macd_rsi_kdj_show_together.xml index b5b9ad2311bd6ccd6492ed5ef9e720de9b4ae931..90a26d8f13dff3c2118bc9c1e74eece5b47320c5 100644 --- a/app/src/main/res/layout/activity_macd_rsi_kdj_show_together.xml +++ b/entry/src/main/resources/base/layout/slice_macd_rsi_kdj_show_together.xml @@ -1,18 +1,18 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slice_main.xml b/entry/src/main/resources/base/layout/slice_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..f676705ed6d7b04e11685e2dd7d3931ba64b1e45 --- /dev/null +++ b/entry/src/main/resources/base/layout/slice_main.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slice_simple_timeline.xml b/entry/src/main/resources/base/layout/slice_simple_timeline.xml new file mode 100644 index 0000000000000000000000000000000000000000..09b95e3757d8d49410972b24774a868deb3275d7 --- /dev/null +++ b/entry/src/main/resources/base/layout/slice_simple_timeline.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slice_with_fragment_and_tablayout_switcher.xml b/entry/src/main/resources/base/layout/slice_with_fragment_and_tablayout_switcher.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7016e78d5f181565d9ced1fa84b8355ded032bc --- /dev/null +++ b/entry/src/main/resources/base/layout/slice_with_fragment_and_tablayout_switcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slice_with_pull_to_refresh.xml b/entry/src/main/resources/base/layout/slice_with_pull_to_refresh.xml new file mode 100644 index 0000000000000000000000000000000000000000..56e02a7be7cb5f03bfa839bb59c347340ff1886e --- /dev/null +++ b/entry/src/main/resources/base/layout/slice_with_pull_to_refresh.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/slider_kline.xml b/entry/src/main/resources/base/layout/slider_kline.xml new file mode 100644 index 0000000000000000000000000000000000000000..4e5a20d7197b3eca92e70cb13d6fd463d94832e7 --- /dev/null +++ b/entry/src/main/resources/base/layout/slider_kline.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/layout/toast_dialog_layout.xml b/entry/src/main/resources/base/layout/toast_dialog_layout.xml new file mode 100644 index 0000000000000000000000000000000000000000..1864056fba7b73fdbed4d297339f812e96c099ac --- /dev/null +++ b/entry/src/main/resources/base/layout/toast_dialog_layout.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/entry/src/main/resources/base/media/icon.png differ diff --git a/entry/src/main/resources/base/media/rotate.png b/entry/src/main/resources/base/media/rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..b0782ba17a1d27be9c4e337b08a17f568e2f454c Binary files /dev/null and b/entry/src/main/resources/base/media/rotate.png differ diff --git a/app/src/main/res/raw/client.p12 b/entry/src/main/resources/base/profile/client.p12 similarity index 100% rename from app/src/main/res/raw/client.p12 rename to entry/src/main/resources/base/profile/client.p12 diff --git a/app/src/main/res/raw/mytruststore.bks b/entry/src/main/resources/base/profile/mytruststore.bks similarity index 100% rename from app/src/main/res/raw/mytruststore.bks rename to entry/src/main/resources/base/profile/mytruststore.bks diff --git a/app/src/main/assets/kline1.txt b/entry/src/main/resources/rawfile/kline1.txt similarity index 100% rename from app/src/main/assets/kline1.txt rename to entry/src/main/resources/rawfile/kline1.txt diff --git a/entry/src/ohosTest/config.json b/entry/src/ohosTest/config.json new file mode 100644 index 0000000000000000000000000000000000000000..0e2354e957b6e7578044b30821509fc1d0c55f00 --- /dev/null +++ b/entry/src/ohosTest/config.json @@ -0,0 +1,41 @@ +{ + "app": { + "bundleName": "com.wordplat.quickstart", + "vendor": "wordplat", + "version": { + "code": 1, + "name": "1.0" + }, + "apiVersion": { + "compatible": 5, + "target": 5, + "releaseType": "Beta1" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.wordplat.quickstart", + "name": "testModule", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry_test", + "moduleType": "feature", + "installationFree": true + }, + "abilities": [ + { + "name": "decc.testkit.runner.EntryAbility", + "description": "Test Entry Ability", + "icon": "$media:icon", + "label": "$string:app_name", + "launchType": "standard", + "orientation": "landscape", + "visible": true, + "type": "page" + } + ] + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/java/com/wordplat/quickstart/ExampleOhosTest.java b/entry/src/ohosTest/java/com/wordplat/quickstart/ExampleOhosTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c4972164bce26776af3dd3b16f5873fed4818bc9 --- /dev/null +++ b/entry/src/ohosTest/java/com/wordplat/quickstart/ExampleOhosTest.java @@ -0,0 +1,14 @@ +package com.wordplat.quickstart; + +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExampleOhosTest { + @Test + public void testBundleName() { + final String actualBundleName = AbilityDelegatorRegistry.getArguments().getTestBundleName(); + assertEquals("com.wordplat.quickstart", actualBundleName); + } +} \ No newline at end of file diff --git a/entry/src/test/java/com/wordplat/quickstart/ExampleTest.java b/entry/src/test/java/com/wordplat/quickstart/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f4a081abc9c81ab39c8767058f4a040985f05251 --- /dev/null +++ b/entry/src/test/java/com/wordplat/quickstart/ExampleTest.java @@ -0,0 +1,9 @@ +package com.wordplat.quickstart; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/gradle.properties b/gradle.properties index 66a46dda674af0c695c88089b19db61390e42efe..bdc983f117cf6bee9ad35e014fda5cbf4a0a54ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,13 @@ # Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: +# IDE (e.g. DevEco Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -android.useDeprecatedNdk=true - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - +# If the Chinese output is garbled, please configure the following parameter. +# org.gradle.jvmargs=-Dfile.encoding=GBK LIB_VERSION_CODE=6 LIB_VERSION_NAME=0.1.5 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372aef5e24af05341d49695ee84e5f9b594659..490fda8577df6c95960ba7077c43220e5bb2c0d9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8542cb07c4f3edc22b09377c5755b332402ada1a..f59159e865d4b59feb1b8c44b001f62fc5d58df4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Jan 24 17:01:14 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://repo.huaweicloud.com/gradle/gradle-6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip diff --git a/ikvStockChart/build.gradle b/ikvStockChart/build.gradle index 82ee32c082b2bd85c356757956fd642942e73b21..25ddf0cf1daa8f1533639ebc05ce352f4019b8ff 100644 --- a/ikvStockChart/build.gradle +++ b/ikvStockChart/build.gradle @@ -1,25 +1,21 @@ -apply plugin: 'com.android.library' - -android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" - +apply plugin: 'com.huawei.ohos.library' +ohos { + compileSdkVersion 5 defaultConfig { - minSdkVersion 14 - targetSdkVersion 25 - versionCode LIB_VERSION_CODE as int - versionName LIB_VERSION_NAME as String + compatibleSdkVersion 5 } buildTypes { release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } } } + } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:support-v4:25.1.0' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.13' } diff --git a/ikvStockChart/proguard-rules.pro b/ikvStockChart/proguard-rules.pro index 0b967251a816a33beeb7b16f4daca951f92f97a9..f7666e47561d514b2a76d5a7dfbb43ede86da92a 100644 --- a/ikvStockChart/proguard-rules.pro +++ b/ikvStockChart/proguard-rules.pro @@ -1,25 +1 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/afon/Share/android-sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/ikvStockChart/src/main/AndroidManifest.xml b/ikvStockChart/src/main/AndroidManifest.xml deleted file mode 100644 index 64b76014687ea8960b8759fced6ead60c544eb65..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/ikvStockChart/src/main/config.json b/ikvStockChart/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..ebec6d408de741b948c1be5b78b95d80a3604db8 --- /dev/null +++ b/ikvStockChart/src/main/config.json @@ -0,0 +1,27 @@ +{ + "app": { + "bundleName": "com.wordplat.quickstart", + "vendor": "wordplat", + "version": { + "code": 6, + "name": "0.1.5" + }, + "apiVersion": { + "compatible": 5, + "target": 5, + "releaseType": "Beta1" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.wordplat.ikvstockchart", + "deviceType": [ + "phone" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "ikvstockchart", + "moduleType": "har" + } + } +} \ No newline at end of file diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineLayout.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineLayout.java index 60c49fb99dadf9ceb520b4542841bb27f9ad8abc..5239eeca1831491f42f03f9fd883b33d14d3c49f 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineLayout.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineLayout.java @@ -18,45 +18,29 @@ package com.wordplat.ikvstockchart; -import android.content.Context; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; - import com.wordplat.ikvstockchart.compat.ViewUtils; -import com.wordplat.ikvstockchart.drawing.BOLLDrawing; -import com.wordplat.ikvstockchart.drawing.HighlightDrawing; -import com.wordplat.ikvstockchart.drawing.KDJDrawing; -import com.wordplat.ikvstockchart.drawing.KLineVolumeDrawing; -import com.wordplat.ikvstockchart.drawing.KLineVolumeHighlightDrawing; -import com.wordplat.ikvstockchart.drawing.MACDDrawing; -import com.wordplat.ikvstockchart.drawing.RSIDrawing; -import com.wordplat.ikvstockchart.drawing.StockIndexYLabelDrawing; -import com.wordplat.ikvstockchart.entry.Entry; -import com.wordplat.ikvstockchart.entry.StockBOLLIndex; -import com.wordplat.ikvstockchart.entry.StockKDJIndex; -import com.wordplat.ikvstockchart.entry.StockKLineVolumeIndex; -import com.wordplat.ikvstockchart.entry.StockMACDIndex; -import com.wordplat.ikvstockchart.entry.StockRSIIndex; +import com.wordplat.ikvstockchart.drawing.*; +import com.wordplat.ikvstockchart.entry.*; import com.wordplat.ikvstockchart.marker.XAxisTextMarkerView; import com.wordplat.ikvstockchart.marker.YAxisTextMarkerView; import com.wordplat.ikvstockchart.render.KLineRender; +import ohos.agp.components.*; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.app.Context; +import ohos.multimodalinput.event.TouchEvent; + /** *

InteractiveKLineLayout

*

Date: 2017/3/22

* - * @deprecated 这是一个含有股票技术指标的K线图示例,建议不要使用这个类用于真实项目中,此示例对如何编写自己的自定义布局提供参考。 * @author afon + * @deprecated 这是一个含有股票技术指标的K线图示例,建议不要使用这个类用于真实项目中,此示例对如何编写自己的自定义布局提供参考。 */ @Deprecated -public class InteractiveKLineLayout extends FrameLayout implements View.OnClickListener { - private static final String TAG = "InteractiveKLineLayout"; +public class InteractiveKLineLayout extends StackLayout implements Component.ClickedListener { private Context context; @@ -72,36 +56,37 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL private int stockMarkerViewHeight; private int stockIndexViewHeight; private int stockIndexTabHeight; - private RectF currentRect; + private RectFloat currentRect; - private RadioGroup But_Group; + private RadioContainer But_Group; private RadioButton MACD_But; private RadioButton RSI_But; private RadioButton KDJ_But; private RadioButton BOLL_But; + private boolean isOnClickButton; public InteractiveKLineLayout(Context context) { this(context, null); } - public InteractiveKLineLayout(Context context, AttributeSet attrs) { + public InteractiveKLineLayout(Context context, AttrSet attrs) { this(context, attrs, 0); } - public InteractiveKLineLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + public InteractiveKLineLayout(Context context, AttrSet attrs, int defStyleAttr) { + super(context, attrs, ""); this.context = context; - stockMarkerViewHeight = context.getResources().getDimensionPixelOffset(R.dimen.stock_marker_view_height); - stockIndexViewHeight = context.getResources().getDimensionPixelOffset(R.dimen.stock_index_view_height); - stockIndexTabHeight = context.getResources().getDimensionPixelOffset(R.dimen.stock_index_tab_height); + stockMarkerViewHeight = ViewUtils.vpToPx(context,15);; + stockIndexViewHeight = ViewUtils.vpToPx(context,100); + stockIndexTabHeight = ViewUtils.vpToPx(context,20);; initUI(context, attrs, defStyleAttr); } - private void initUI(Context context, AttributeSet attrs, int defStyleAttr) { - kLineView = new InteractiveKLineView(context); + private void initUI(Context context, AttrSet attrs, int defStyleAttr) { + kLineView = new InteractiveKLineView(context,attrs); kLineRender = (KLineRender) kLineView.getRender(); kLineRender.setSizeColor(ViewUtils.getSizeColor(context, attrs, defStyleAttr)); @@ -122,16 +107,15 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL } @Override - public void onSingleTap(MotionEvent e, float x, float y) { + public void onSingleTap(TouchEvent e, float x, float y) { if (kLineHandler != null) { kLineHandler.onSingleTap(e, x, y); } - onTabClick(x, y); } @Override - public void onDoubleTap(MotionEvent e, float x, float y) { + public void onDoubleTap(TouchEvent e, float x, float y) { if (kLineHandler != null) { kLineHandler.onDoubleTap(e, x, y); } @@ -152,13 +136,17 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL } }); - // 成交量 + /** + * 成交量 + */ StockKLineVolumeIndex kLineVolumeIndex = new StockKLineVolumeIndex(stockIndexViewHeight); kLineVolumeIndex.addDrawing(new KLineVolumeDrawing()); kLineVolumeIndex.addDrawing(new KLineVolumeHighlightDrawing()); kLineRender.addStockIndex(kLineVolumeIndex); - // MACD + /** + * MACD + */ HighlightDrawing macdHighlightDrawing = new HighlightDrawing(); macdHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -169,7 +157,9 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL macdIndex.setPaddingTop(stockIndexTabHeight); kLineRender.addStockIndex(macdIndex); - // RSI + /** + * RSI + */ HighlightDrawing rsiHighlightDrawing = new HighlightDrawing(); rsiHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -180,7 +170,9 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL rsiIndex.setPaddingTop(stockIndexTabHeight); kLineRender.addStockIndex(rsiIndex); - // KDJ + /** + * KDJ + */ HighlightDrawing kdjHighlightDrawing = new HighlightDrawing(); kdjHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -191,7 +183,9 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL kdjIndex.setPaddingTop(stockIndexTabHeight); kLineRender.addStockIndex(kdjIndex); - // BOLL + /** + * BOLL + */ HighlightDrawing bollHighlightDrawing = new HighlightDrawing(); bollHighlightDrawing.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); @@ -205,23 +199,20 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL kLineRender.addMarkerView(new YAxisTextMarkerView(stockMarkerViewHeight)); kLineRender.addMarkerView(new XAxisTextMarkerView(stockMarkerViewHeight)); - addView(kLineView); + addComponent(kLineView); } - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - But_Group = (RadioGroup) findViewById(R.id.But_Group); - MACD_But = (RadioButton) findViewById(R.id.MACD_But); - RSI_But = (RadioButton) findViewById(R.id.RSI_But); - KDJ_But = (RadioButton) findViewById(R.id.KDJ_But); - BOLL_But = (RadioButton) findViewById(R.id.BOLL_But); + public void setClick(RadioContainer but_Group, RadioButton MACD_But, RadioButton RSI_But, RadioButton KDJ_But, RadioButton BOLL_But) { + this.But_Group = but_Group; + this.MACD_But = MACD_But; + this.RSI_But = RSI_But; + this.KDJ_But = KDJ_But; + this.BOLL_But = BOLL_But; - MACD_But.setOnClickListener(this); - RSI_But.setOnClickListener(this); - KDJ_But.setOnClickListener(this); - BOLL_But.setOnClickListener(this); + MACD_But.setClickedListener(this); + RSI_But.setClickedListener(this); + KDJ_But.setClickedListener(this); + BOLL_But.setClickedListener(this); showMACD(); } @@ -230,12 +221,24 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL return kLineView; } + /** + * setKLineHandler + * + * @param kLineHandler + */ public void setKLineHandler(KLineHandler kLineHandler) { this.kLineHandler = kLineHandler; } private void onTabClick(float x, float y) { - if (currentRect.contains(x, y)) { + if (isOnClickButton) { + isOnClickButton = false; + return; + } + if (currentRect == null) { + return; + } + if (currentRect.isInclude(x, y)) { if (macdIndex.isEnable()) { showRSI(); } else if (rsiIndex.isEnable()) { @@ -254,76 +257,91 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL } } - @Override - public void onClick(View v) { - final int id = v.getId(); - - if (id == R.id.MACD_But) { - showMACD(); - - } else if (id == R.id.RSI_But) { - showRSI(); - - } else if (id == R.id.KDJ_But) { - showKDJ(); - - } else if (id == R.id.BOLL_But) { - showBOLL(); - - } - - if (kLineHandler != null) { - kLineHandler.onCancelHighlight(); - } - - kLineView.notifyDataSetChanged(); - } - + /** + * showMACD + */ public void showMACD() { macdIndex.setEnable(true); rsiIndex.setEnable(false); kdjIndex.setEnable(false); bollIndex.setEnable(false); - But_Group.clearCheck(); MACD_But.setChecked(true); + RSI_But.setChecked(false); + KDJ_But.setChecked(false); + BOLL_But.setChecked(false); + + MACD_But.setTextColor(new Color(Color.rgb(30,130,210))); + RSI_But.setTextColor(new Color(Color.rgb(136,136,136))); + KDJ_But.setTextColor(new Color(Color.rgb(136,136,136))); + BOLL_But.setTextColor(new Color(Color.rgb(136,136,136))); currentRect = macdIndex.getRect(); } + /** + * showRSI + */ public void showRSI() { macdIndex.setEnable(false); rsiIndex.setEnable(true); kdjIndex.setEnable(false); bollIndex.setEnable(false); - But_Group.clearCheck(); + MACD_But.setChecked(false); RSI_But.setChecked(true); + KDJ_But.setChecked(false); + BOLL_But.setChecked(false); + + MACD_But.setTextColor(new Color(Color.rgb(136,136,136))); + RSI_But.setTextColor(new Color(Color.rgb(30,130,210))); + KDJ_But.setTextColor(new Color(Color.rgb(136,136,136))); + BOLL_But.setTextColor(new Color(Color.rgb(136,136,136))); currentRect = rsiIndex.getRect(); } + /** + * showKDJ + */ public void showKDJ() { macdIndex.setEnable(false); rsiIndex.setEnable(false); kdjIndex.setEnable(true); bollIndex.setEnable(false); - But_Group.clearCheck(); + MACD_But.setChecked(false); + RSI_But.setChecked(false); KDJ_But.setChecked(true); + BOLL_But.setChecked(false); + + MACD_But.setTextColor(new Color(Color.rgb(136,136,136))); + RSI_But.setTextColor(new Color(Color.rgb(136,136,136))); + KDJ_But.setTextColor(new Color(Color.rgb(30,130,210))); + BOLL_But.setTextColor(new Color(Color.rgb(136,136,136))); currentRect = kdjIndex.getRect(); } + /** + * showBOLL + */ public void showBOLL() { macdIndex.setEnable(false); rsiIndex.setEnable(false); kdjIndex.setEnable(false); bollIndex.setEnable(true); - But_Group.clearCheck(); + MACD_But.setChecked(false); + RSI_But.setChecked(false); + KDJ_But.setChecked(false); BOLL_But.setChecked(true); + MACD_But.setTextColor(new Color(Color.rgb(136,136,136))); + RSI_But.setTextColor(new Color(Color.rgb(136,136,136))); + KDJ_But.setTextColor(new Color(Color.rgb(136,136,136))); + BOLL_But.setTextColor(new Color(Color.rgb(30,130,210))); + currentRect = bollIndex.getRect(); } @@ -342,4 +360,25 @@ public class InteractiveKLineLayout extends FrameLayout implements View.OnClickL public boolean isShownBOLL() { return bollIndex.isEnable(); } + + @Override + public void onClick(Component component) { + final int id = component.getId(); + isOnClickButton = true; + if (id == ResourceTable.Id_MACD_But) { + showMACD(); + } else if (id == ResourceTable.Id_RSI_But) { + showRSI(); + } else if (id == ResourceTable.Id_KDJ_But) { + showKDJ(); + } else if (id == ResourceTable.Id_BOLL_But) { + showBOLL(); + } + + if (kLineHandler != null) { + kLineHandler.onCancelHighlight(); + } + + kLineView.notifyDataSetChanged(); + } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineView.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineView.java index bc3379b1b26b155a502da2af0c979c3e9de79464..ab88ae2e14c32379c8d61a2ada5f72a924e84411 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineView.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/InteractiveKLineView.java @@ -18,64 +18,118 @@ package com.wordplat.ikvstockchart; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.RectF; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ScrollerCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.animation.Interpolator; +import com.wordplat.ikvstockchart.compat.GestureMoveActionCompat; import com.wordplat.ikvstockchart.compat.ViewUtils; +import com.wordplat.ikvstockchart.detector.GestureDetector; +import com.wordplat.ikvstockchart.detector.ScaleGestureDetector; +import com.wordplat.ikvstockchart.detector.ViewConfiguration; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.StockDataTest; -import com.wordplat.ikvstockchart.compat.GestureMoveActionCompat; import com.wordplat.ikvstockchart.render.AbstractRender; import com.wordplat.ikvstockchart.render.KLineRender; +import ohos.agp.components.AttrSet; +import ohos.agp.components.Component; +import ohos.agp.components.ScrollHelper; +import ohos.agp.render.Canvas; +import ohos.agp.utils.RectFloat; +import ohos.agp.window.dialog.ToastDialog; +import ohos.app.Context; +import ohos.app.dispatcher.TaskDispatcher; +import ohos.eventhandler.EventHandler; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.MmiPoint; +import ohos.multimodalinput.event.TouchEvent; /** *

交互式 K 线图

*

Date: 2017/3/10

* * @author afon + * @since 2021-05-09 */ +public class InteractiveKLineView extends Component implements Component.DrawTask { + static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "AbstractRender"); + private static final boolean DEBUG = true; + + /** + * 控制是否可以进行下拉刷新 + */ + private boolean isRefresh; + /** + * 与滚动控制、滑动加载数据相关的属性 + */ + /** + * dragging 松手之后回中的时间,单位:毫秒 + */ + private static final int OVERSCROLL_DURATION = 500; + /** + * dragging 的偏移量大于此值时即是一个有效的滑动加载 + */ + private static final int OVERSCROLL_THRESHOLD = 50; + /** + * 空闲 + */ + private static final int KLINE_STATUS_IDLE = 0; + /** + * 放手,回弹到 loading 位置 + */ + private static final int KLINE_STATUS_RELEASE_BACK = 2; + /** + * 加载中 + */ + private static final int KLINE_STATUS_LOADING = 3; + /** + * 加载结束,回弹到初始位置 + */ + private static final int KLINE_STATUS_SPRING_BACK = 4; + private final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + float f = detector.getScaleFactor(); -public class InteractiveKLineView extends View { - private static final String TAG = "InteractiveKLineView"; - private static final boolean DEBUG = false; + if (f < 1.0f) { + render.zoomOut(detector.getFocusX(), detector.getFocusY()); + } else if (f > 1.0f) { + render.zoomIn(detector.getFocusX(), detector.getFocusY()); + } + notifyDataSetChanged(); + } + }); - // 与视图大小相关的属性 - private final RectF viewRect; + private GestureMoveActionCompat gestureCompat = new GestureMoveActionCompat(null); + /** + * 与视图大小相关的属性 + */ + private final RectFloat viewRect; private final float viewPadding; - // 与数据加载、渲染相关的属性 + /** + * 与数据加载、渲染相关的属性 + */ private AbstractRender render; private EntrySet entrySet; private KLineHandler kLineHandler; - // 与滚动控制、滑动加载数据相关的属性 - private static final int OVERSCROLL_DURATION = 500; // dragging 松手之后回中的时间,单位:毫秒 - private static final int OVERSCROLL_THRESHOLD = 220; // dragging 的偏移量大于此值时即是一个有效的滑动加载 - private static final int KLINE_STATUS_IDLE = 0; // 空闲 - private static final int KLINE_STATUS_RELEASE_BACK = 2; // 放手,回弹到 loading 位置 - private static final int KLINE_STATUS_LOADING = 3; // 加载中 - private static final int KLINE_STATUS_SPRING_BACK = 4; // 加载结束,回弹到初始位置 + private int kLineStatus = KLINE_STATUS_IDLE; private int lastFlingX = 0; private int lastScrollDx = 0; - private int lastEntrySize = 0; // 上一次的 entry 列表大小,用于判断是否成功加载了数据 - private int lastHighlightIndex = -1; // 上一次高亮的 entry 索引,用于减少回调 - private final ScrollerCompat scroller; + /** + * 上一次的 entry 列表大小,用于判断是否成功加载了数据 + */ + private int lastEntrySize = 0; + /** + * 上一次高亮的 entry 索引,用于减少回调 + */ + private int lastHighlightIndex = -1; + private final ScrollHelper scroller; - // 与手势控制相关的属性 + /** + * 与手势控制相关的属性 + */ private boolean onTouch = false; private boolean onLongPress = false; private boolean onDoubleFingerPress = false; @@ -83,48 +137,89 @@ public class InteractiveKLineView extends View { private boolean onDragging = false; private boolean enableLeftRefresh = true; private boolean enableRightRefresh = true; + private long INVALIDATE_TIME_INTERVAL = 2000; + private boolean isEmpty; + /** + * InteractiveKLineView + * + * @param context + */ public InteractiveKLineView(Context context) { this(context, null); } - public InteractiveKLineView(Context context, AttributeSet attrs) { + /** + * InteractiveKLineView + * + * @param context + * @param attrs + */ + public InteractiveKLineView(Context context, AttrSet attrs) { this(context, attrs, 0); } - public InteractiveKLineView(Context context, AttributeSet attrs, int defStyleAttr) { + /** + * InteractiveKLineView + * + * @param context + * @param attrs + * @param defStyleAttr + */ + public InteractiveKLineView(Context context, AttrSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - viewRect = new RectF(); - viewPadding = ViewUtils.dpTopx(context, 10); + viewRect = new RectFloat(); + viewPadding = ViewUtils.vpToPx(context, 10); render = new KLineRender(context); gestureDetector.setIsLongpressEnabled(true); - int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + int touchSlop = ViewConfiguration.getScaledTouchSlop(); gestureCompat.setTouchSlop(touchSlop); - final Interpolator interpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - scroller = ScrollerCompat.create(context, interpolator); + scroller = new ScrollHelper(); render.setSizeColor(ViewUtils.getSizeColor(context, attrs, defStyleAttr)); + + setListener(); + } + + /** + * 获取是否可以下拉刷新 + * + * @return true + */ + public boolean isRefresh() { + return isRefresh; + } + + /** + * 设置是否可以下拉刷新 + * + * @param refresh + */ + public void setRefresh(boolean refresh) { + isRefresh = refresh; } public void setEntrySet(EntrySet set) { entrySet = set; } + /** + * notifyDataSetChanged + */ public void notifyDataSetChanged() { notifyDataSetChanged(true); } + /** + * notifyDataSetChanged + * + * @param invalidate + */ public void notifyDataSetChanged(boolean invalidate) { render.setViewRect(viewRect); render.onViewRect(viewRect); @@ -135,6 +230,11 @@ public class InteractiveKLineView extends View { } } + /** + * setRender + * + * @param render + */ public void setRender(AbstractRender render) { render.setSizeColor(this.render.getSizeColor()); this.render = render; @@ -148,50 +248,52 @@ public class InteractiveKLineView extends View { this.kLineHandler = kLineHandler; } - public RectF getViewRect() { + public RectFloat getViewRect() { return viewRect; } private final GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { + @Override - public void onLongPress(MotionEvent e) { + public void onLongPress(TouchEvent e) { if (onTouch) { onLongPress = true; - highlight(e.getX(), e.getY()); + MmiPoint point = e.getPointerPosition(e.getIndex()); + highlight(point.getX(), point.getY()); } } @Override - public boolean onDoubleTap(MotionEvent e) { + public boolean onDoubleTap(TouchEvent e) { if (kLineHandler != null) { - kLineHandler.onDoubleTap(e, e.getX(), e.getY()); + kLineHandler.onDoubleTap(e, e.getPointerPosition(e.getIndex()).getX(), e.getPointerPosition(e.getIndex()).getY()); } return true; } @Override - public boolean onSingleTapConfirmed(MotionEvent e) { + public boolean onSingleTapConfirmed(TouchEvent e) { if (kLineHandler != null) { - kLineHandler.onSingleTap(e, e.getX(), e.getY()); + kLineHandler.onSingleTap(e, e.getPointerPosition(e.getIndex()).getX(), e.getPointerPosition(e.getIndex()).getY()); } return true; } @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + public boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY) { if (!onLongPress && !onDoubleFingerPress && !onVerticalMove) { if (onDragging && !render.canScroll(distanceX) && render.canDragging(distanceX)) { dragging((int) distanceX); if (DEBUG) { - Log.v(TAG, "##d dragging: -------> " + distanceX + ", maxScrollOffset = " + HiLog.info(LOG_LABEL, "##d dragging: -------> " + distanceX + ", maxScrollOffset = " + render.getMaxScrollOffset() + ", tranX = " + render.getCurrentTransX()); } } else { scroll((int) distanceX); if (DEBUG) { - Log.v(TAG, "##d scroll: -------> " + distanceX + ", maxScrollOffset = " + HiLog.info(LOG_LABEL, "##d scroll: -------> " + distanceX + ", maxScrollOffset = " + render.getMaxScrollOffset() + ", tranX = " + render.getCurrentTransX()); } } @@ -202,14 +304,14 @@ public class InteractiveKLineView extends View { } @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + public boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, float velocityY, float scrollY0, float scrollY1) { lastFlingX = 0; if (!onLongPress && !onDoubleFingerPress && !onVerticalMove && render.canScroll(0)) { if (DEBUG) { - Log.d(TAG, "##d onFling: ------->"); + HiLog.info(LOG_LABEL, "##d onFling: ------->"); } - scroller.fling(0, 0, (int) -velocityX, 0, + scroller.doFling(0, 0, (int) -velocityX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); return true; } else { @@ -218,23 +320,9 @@ public class InteractiveKLineView extends View { } }); - private final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - float f = detector.getScaleFactor(); - - if (f < 1.0f) { - render.zoomOut(detector.getFocusX(), detector.getFocusY()); - } else if (f > 1.0f) { - render.zoomIn(detector.getFocusX(), detector.getFocusY()); - } - } - }); - - private GestureMoveActionCompat gestureCompat = new GestureMoveActionCompat(null); private void highlight(float x, float y) { - render.onHighlight(x, y); + render.onHighlight(x, y - 128); invalidate(); int highlightIndex = render.getEntrySet().getHighlightIndex(); @@ -265,7 +353,14 @@ public class InteractiveKLineView extends View { */ public void scroll(float dx) { render.scroll(dx); - invalidate(); + notifyDataSetChanged(); + /** + * invalidate(); + */ + } + + public void isEmpty(boolean isEmpty) { + this.isEmpty = isEmpty; } /** @@ -274,10 +369,85 @@ public class InteractiveKLineView extends View { * @param dx 变化量 */ private void dragging(float dx) { - if (render.getMaxScrollOffset() < 0 || dx < 0) { - render.updateCurrentTransX(dx); - render.updateOverScrollOffset(dx); - invalidate(); + if (isEmpty) { + if (dx < 0) { + render.updateCurrentTransX(dx); + render.updateOverScrollOffset(dx); + notifyDataSetChanged(); + if (Math.abs(dx) > OVERSCROLL_THRESHOLD) { + if (enableLeftRefresh && dx > 0) { + lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD; + } + if (enableRightRefresh && dx < 0) { + lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD; + } + } + kLineStatus = KLINE_STATUS_LOADING; + if (kLineHandler != null) { + lastEntrySize = entrySet.getEntryList().size(); + if (lastScrollDx > 0) { + kLineHandler.onRightRefresh(); + } else if (lastScrollDx < 0) { + kLineHandler.onLeftRefresh(); + } + } else { + refreshComplete(); + } + notifyDataSetChanged(); + } else if (dx > 0){ + render.updateCurrentTransX(dx); + render.updateOverScrollOffset(dx); + notifyDataSetChanged(); + if (Math.abs(dx) > OVERSCROLL_THRESHOLD) { + if (enableLeftRefresh && dx > 0) { + lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD; + } + if (enableRightRefresh && dx < 0) { + lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD; + } + } + kLineStatus = KLINE_STATUS_LOADING; + if (kLineHandler != null) { + lastEntrySize = entrySet.getEntryList().size(); + if (lastScrollDx > 0) { + kLineHandler.onRightRefresh(); + } else if (lastScrollDx < 0) { + kLineHandler.onLeftRefresh(); + } + } else { + refreshComplete(); + } + notifyDataSetChanged(); + } + } else { + if (render.getMaxScrollOffset() < 0 || dx < 0) { + render.updateCurrentTransX(dx); + render.updateOverScrollOffset(dx); + notifyDataSetChanged(); + if (Math.abs(dx) > OVERSCROLL_THRESHOLD) { + if (enableLeftRefresh && dx > 0) { + lastScrollDx = (int) dx - OVERSCROLL_THRESHOLD; + } + if (enableRightRefresh && dx < 0) { + lastScrollDx = (int) dx + OVERSCROLL_THRESHOLD; + } + } + kLineStatus = KLINE_STATUS_LOADING; + if (kLineHandler != null) { + lastEntrySize = entrySet.getEntryList().size(); + if (lastScrollDx > 0) { + kLineHandler.onRightRefresh(); + } else if (lastScrollDx < 0) { + kLineHandler.onLeftRefresh(); + } + } else { + refreshComplete(); + } + notifyDataSetChanged(); + } + /** + * invalidate(); + */ } } @@ -305,115 +475,6 @@ public class InteractiveKLineView extends View { } } - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - viewRect.set(viewPadding, viewPadding, w - viewPadding, h - viewPadding); - - if (DEBUG) { - Log.i(TAG, "##d onSizeChanged: " + viewRect); - } - - // 在 Android Studio 预览模式下,添加一些测试数据,可以把 K 线图预览出来 - if (isInEditMode()) { - EntrySet entrySet = StockDataTest.parseKLineData(StockDataTest.KLINE); - if (entrySet != null) { - entrySet.computeStockIndex(); - } - - setEntrySet(entrySet); - } - if (entrySet == null) { - entrySet = new EntrySet(); - } - - notifyDataSetChanged(); - } - - @Override - public void computeScroll() { - if (onVerticalMove) { - return ; - } - - if (scroller.computeScrollOffset()) { - final int x = scroller.getCurrX(); - final int dx = x - lastFlingX; - lastFlingX = x; - - if (onTouch) { - scroller.abortAnimation(); - } else { - if (kLineStatus == KLINE_STATUS_RELEASE_BACK) { - releaseBack(dx); - } else if (kLineStatus == KLINE_STATUS_SPRING_BACK) { - springBack(dx); - } else { - scroll(dx); - } - - if (render.canScroll(dx) && !scroller.isFinished()) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - if (DEBUG) { - Log.i(TAG, "##d computeScrollOffset: canScroll = " + render.canScroll(dx) - + ", overScrollOffset = " + render.getOverScrollOffset() - + ", dx = " + dx + ", tranX = " + render.getCurrentTransX()); - } - } else { - final float overScrollOffset = render.getOverScrollOffset(); - if (DEBUG) { - Log.d(TAG, "##d overScrollOffset: canScroll = " + render.canScroll(0) - + ", overScrollOffset = " + overScrollOffset); - } - - if (!onTouch && overScrollOffset != 0 && kLineStatus == KLINE_STATUS_IDLE) { - lastScrollDx = 0; - float dx = overScrollOffset; - - if (Math.abs(overScrollOffset) > OVERSCROLL_THRESHOLD) { - if (enableLeftRefresh && overScrollOffset > 0) { - lastScrollDx = (int) overScrollOffset - OVERSCROLL_THRESHOLD; - - dx = lastScrollDx; - } - - if (enableRightRefresh && overScrollOffset < 0) { - lastScrollDx = (int) overScrollOffset + OVERSCROLL_THRESHOLD; - - dx = lastScrollDx; - } - } - - if (DEBUG) { - Log.d(TAG, "##d startScroll: LOADING... dx = " + dx); - } - - kLineStatus = KLINE_STATUS_RELEASE_BACK; - lastFlingX = 0; - scroller.startScroll(0, 0, (int) dx, 0, OVERSCROLL_DURATION); - ViewCompat.postInvalidateOnAnimation(this); - - } else if (kLineStatus == KLINE_STATUS_RELEASE_BACK) { - kLineStatus = KLINE_STATUS_LOADING; - - if (kLineHandler != null) { - lastEntrySize = entrySet.getEntryList().size(); - if (lastScrollDx > 0) { - kLineHandler.onLeftRefresh(); - } else if (lastScrollDx < 0) { - kLineHandler.onRightRefresh(); - } - } else { - refreshComplete(); - } - } else { - kLineStatus = KLINE_STATUS_IDLE; - } - } - } - /** * 加载完成 */ @@ -430,14 +491,21 @@ public class InteractiveKLineView extends View { final int overScrollOffset = (int) render.getOverScrollOffset(); if (DEBUG) { - Log.i(TAG, "##d refreshComplete: refreshComplete... overScrollOffset = " + overScrollOffset); + HiLog.info(LOG_LABEL, "##d refreshComplete: refreshComplete... overScrollOffset = " + overScrollOffset); } if (overScrollOffset != 0) { kLineStatus = KLINE_STATUS_SPRING_BACK; lastFlingX = 0; - scroller.startScroll(0, 0, reverse ? -overScrollOffset : overScrollOffset, 0, OVERSCROLL_DURATION); - ViewCompat.postInvalidateOnAnimation(this); + scroller.startScroll(0, 0, reverse ? -overScrollOffset : overScrollOffset, 0); + TaskDispatcher globalTaskDispatcher = mContext.getUITaskDispatcher(); + Runnable runnable = new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }; + globalTaskDispatcher.delayDispatch(runnable, INVALIDATE_TIME_INTERVAL); } } @@ -457,70 +525,267 @@ public class InteractiveKLineView extends View { return render.isHighlight(); } - @Override - public boolean dispatchTouchEvent(MotionEvent event) { - boolean onHorizontalMove = gestureCompat.onTouchEvent(event, event.getX(), event.getY()); - final int action = MotionEventCompat.getActionMasked(event); + private void setListener() { + gestureCompat = new GestureMoveActionCompat(new GestureMoveActionCompat.OnGestureMoveListener() { + @Override + public void onHorizontalMove(TouchEvent e, float x, float y) { - onVerticalMove = false; + } - if (action == MotionEvent.ACTION_MOVE) { - if (!onHorizontalMove && !onLongPress && !onDoubleFingerPress && gestureCompat.isDragging()) { - onTouch = false; - onVerticalMove = true; + @Override + public void onVerticalMove(TouchEvent e, float x, float y) { + final int action = e.getAction(); + final boolean pointerUp = e.getAction() == TouchEvent.OTHER_POINT_UP; + final int skipIndex = pointerUp ? e.getIndex() : -1; + + float sumX = 0, sumY = 0; + final int count = e.getPointerCount(); + /** + * 把所有还在触摸的手指的位置x,y加起来,后面求平均数,算出中心焦点 + */ + for (int i = 0; i < count; i++) { + if (skipIndex == i) { + /** + * 跳过非主要指针的抬起动作 + */ + continue; + } + sumY += e.getPointerPosition(i).getY(); + } + final int div = pointerUp ? count - 1 : count; + /** + * 求平均值,算出中心焦点 + */ + final float endUpY = sumY / div; + + if (viewOnTouchInterface != null && !onLongPress) { + viewOnTouchInterface.touch(action, endUpY); + } } - } - getParent().requestDisallowInterceptTouchEvent(!onVerticalMove); + @Override + public void onClick(TouchEvent e, float x, float y) { - return super.dispatchTouchEvent(event); - } + } + }); + setLayoutRefreshedListener(new LayoutRefreshedListener() { + @Override + public void onRefreshed(Component component) { + viewRect.left = viewPadding; + viewRect.top = viewPadding; + viewRect.right = component.getWidth() - viewPadding; + viewRect.bottom = component.getHeight() - viewPadding; - @Override - public boolean onTouchEvent(MotionEvent e) { - final int action = MotionEventCompat.getActionMasked(e); - gestureDetector.onTouchEvent(e); - scaleDetector.onTouchEvent(e); + if (DEBUG) { + HiLog.info(LOG_LABEL, "##d onSizeChanged: " + viewRect); + } - switch (action) { - case MotionEvent.ACTION_DOWN: { - onTouch = true; - onDragging = false; - break; - } - case MotionEvent.ACTION_POINTER_DOWN: { - onDoubleFingerPress = true; - break; + if (entrySet == null) { + entrySet = new EntrySet(); + } + notifyDataSetChanged(); } - - case MotionEvent.ACTION_MOVE: { - onDragging = true; - if (onLongPress) { - highlight(e.getX(), e.getY()); + }); + setTouchEventListener(new TouchEventListener() { + @Override + public boolean onTouchEvent(Component component, TouchEvent touchEvent) { + final int action = touchEvent.getAction(); + final boolean pointerUp = touchEvent.getAction() == TouchEvent.OTHER_POINT_UP; + final int skipIndex = pointerUp ? touchEvent.getIndex() : -1; + + float sumX = 0, sumY = 0; + final int count = touchEvent.getPointerCount(); + /** + * 把所有还在触摸的手指的位置x,y加起来,后面求平均数,算出中心焦点 + */ + for (int i = 0; i < count; i++) { + if (skipIndex == i) { + /** + * 跳过非主要指针的抬起动作 + */ + continue; + } + sumY += touchEvent.getPointerPosition(i).getY(); + } + final int div = pointerUp ? count - 1 : count; + /** + * 求平均值,算出中心焦点 + */ + final float endUpY = sumY / div; + boolean onHorizontalMove = gestureCompat.onTouchEvent(touchEvent, touchEvent.getPointerPosition(touchEvent.getIndex()).getX(), touchEvent.getPointerPosition(touchEvent.getIndex()).getY()); + + onVerticalMove = false; + + gestureDetector.onTouchEvent(touchEvent); + scaleDetector.onTouchEvent(touchEvent); + + switch (action) { + case TouchEvent.PRIMARY_POINT_DOWN: { + onTouch = true; + onDragging = false; + if (viewOnTouchInterface != null) { + viewOnTouchInterface.touch(action,endUpY); + } + break; + } + case TouchEvent.OTHER_POINT_DOWN: { + onDoubleFingerPress = true; + break; + } + case TouchEvent.POINT_MOVE: { + onDragging = true; + if (onLongPress) { + highlight(touchEvent.getPointerPosition(touchEvent.getIndex()).getX(), touchEvent.getPointerPosition(touchEvent.getIndex()).getY()); + } + if (!onHorizontalMove && !onLongPress && !onDoubleFingerPress && gestureCompat.isDragging()) { + onTouch = false; + onVerticalMove = true; + } + break; + } + case TouchEvent.PRIMARY_POINT_UP: + if (viewOnTouchInterface != null) { + viewOnTouchInterface.touch(action,endUpY); + } + case TouchEvent.CANCEL: { + onLongPress = false; + onDoubleFingerPress = false; + onTouch = false; + onDragging = false; + cancelHighlight(); + break; + } + default: + break; } - break; + return true; } + }); + addDrawTask(this); + setScrolledListener(new ScrolledListener() { + @Override + public void onContentScrolled(Component component, int i, int i1, int i2, int i3) { + if (onVerticalMove) { + return; + } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - onLongPress = false; - onDoubleFingerPress = false; - onTouch = false; - onDragging = false; + if (scroller.isFinished()) { + final int x = scroller.getCurrValue(VERTICAL); + final int dx = x - lastFlingX; + lastFlingX = x; + + if (onTouch) { + scroller.abortAnimation(); + } else { + if (kLineStatus == KLINE_STATUS_RELEASE_BACK) { + releaseBack(dx); + } else if (kLineStatus == KLINE_STATUS_SPRING_BACK) { + springBack(dx); + } else { + scroll(dx); + } + + if (render.canScroll(dx) && !scroller.isFinished()) { + EventHandler.current().postTask(new Runnable() { + @Override + public void run() { + invalidate(); + } + }, INVALIDATE_TIME_INTERVAL); + } + } - cancelHighlight(); + if (DEBUG) { + HiLog.info(LOG_LABEL, "##d computeScrollOffset: canScroll = " + render.canScroll(dx) + + ", overScrollOffset = " + render.getOverScrollOffset() + + ", dx = " + dx + ", tranX = " + render.getCurrentTransX()); + } + } else { + final float overScrollOffset = render.getOverScrollOffset(); + if (DEBUG) { + HiLog.info(LOG_LABEL, "##d overScrollOffset: canScroll = " + render.canScroll(0) + + ", overScrollOffset = " + overScrollOffset); + } - break; + if (!onTouch && overScrollOffset != 0 && kLineStatus == KLINE_STATUS_IDLE) { + lastScrollDx = 0; + float dx = overScrollOffset; + + if (Math.abs(overScrollOffset) > OVERSCROLL_THRESHOLD) { + if (enableLeftRefresh && overScrollOffset > 0) { + lastScrollDx = (int) overScrollOffset - OVERSCROLL_THRESHOLD; + + dx = lastScrollDx; + } + + if (enableRightRefresh && overScrollOffset < 0) { + lastScrollDx = (int) overScrollOffset + OVERSCROLL_THRESHOLD; + + dx = lastScrollDx; + } + } + + if (DEBUG) { + HiLog.debug(LOG_LABEL, "##d startScroll: LOADING... dx = " + dx); + } + + kLineStatus = KLINE_STATUS_RELEASE_BACK; + lastFlingX = 0; + scroller.startScroll(0, 0, (int) dx, 0); + EventHandler.current().postTask(new Runnable() { + @Override + public void run() { + invalidate(); + } + }, INVALIDATE_TIME_INTERVAL); + + } else if (kLineStatus == KLINE_STATUS_RELEASE_BACK) { + kLineStatus = KLINE_STATUS_LOADING; + + if (kLineHandler != null) { + lastEntrySize = entrySet.getEntryList().size(); + if (lastScrollDx > 0) { + kLineHandler.onLeftRefresh(); + } else if (lastScrollDx < 0) { + kLineHandler.onRightRefresh(); + } + } else { + refreshComplete(); + } + } else { + kLineStatus = KLINE_STATUS_IDLE; + } + } } - } - return true; + @Override + public void scrolledStageUpdate(Component component, int newStage) { + + } + }); } + @Override - protected void onDraw(Canvas canvas) { + public void onDraw(Component component, Canvas canvas) { render.render(canvas); + postLayout(); + } + + public ViewOnTouchInterface viewOnTouchInterface; + + /** + * 定义回调方法 + * + * @param viewOnTouchInterface + */ + public void setOnTouchInterface(ViewOnTouchInterface viewOnTouchInterface) { + this.viewOnTouchInterface = viewOnTouchInterface; + } + + public interface ViewOnTouchInterface { + boolean touch(int getAction, float endUpY); } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/KLineHandler.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/KLineHandler.java index 689de7382c3b87e37d5093b786f841fecb021fd0..b95c0e7749e3728dee8c473927a8046e3b45870c 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/KLineHandler.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/KLineHandler.java @@ -18,28 +18,58 @@ package com.wordplat.ikvstockchart; -import android.view.MotionEvent; import com.wordplat.ikvstockchart.entry.Entry; +import ohos.multimodalinput.event.TouchEvent; /** *

KLineHandler

*

Date: 2017/3/22

* * @author afon + * @since 2021-05-09 */ - public interface KLineHandler { - + /** + * onLeftRefresh + */ void onLeftRefresh(); + /** + * onRightRefresh + */ void onRightRefresh(); - void onSingleTap(MotionEvent e, float x, float y); + /** + * onSingleTap + * + * @param e + * @param x + * @param y + */ + void onSingleTap(TouchEvent e, float x, float y); - void onDoubleTap(MotionEvent e, float x, float y); + /** + * onDoubleTap + * + * @param e + * @param x + * @param y + */ + void onDoubleTap(TouchEvent e, float x, float y); + /** + * onHighlight + * + * @param entry + * @param entryIndex + * @param x + * @param y + */ void onHighlight(Entry entry, int entryIndex, float x, float y); + /** + * onCancelHighlight + */ void onCancelHighlight(); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/SimpleKLineHandler.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/SimpleKLineHandler.java index e3a5b90ed984c78adbd1dce43c64e37e221397fa..c71be925aa4312c4b17daba7d242c2321cfa87fc 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/SimpleKLineHandler.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/SimpleKLineHandler.java @@ -18,32 +18,50 @@ package com.wordplat.ikvstockchart; -import android.view.MotionEvent; - import com.wordplat.ikvstockchart.entry.Entry; +import ohos.multimodalinput.event.TouchEvent; /** *

SimpleKLineHandler

*

Date: 2017/3/22

* * @author afon + * @since 2021-05-09 */ - public class SimpleKLineHandler implements KLineHandler { - + /** + * onLeftRefresh + */ public void onLeftRefresh() { } + /** + * onRightRefresh + */ public void onRightRefresh() { } - public void onSingleTap(MotionEvent e, float x, float y) { + /** + * onSingleTap + * + * @param e + * @param x + * @param y + */ + public void onSingleTap(TouchEvent e, float x, float y) { } - public void onDoubleTap(MotionEvent e, float x, float y) { + /** + * onDoubleTap + * + * @param e + * @param x + * @param y + */ + public void onDoubleTap(TouchEvent e, float x, float y) { } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/XMarkerAlign.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/XMarkerAlign.java index e0fb42f7e5b6a10774b269132c914074e3ebf373..49c2af1fd1dca6a865a4fcce633bd81957a44402 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/XMarkerAlign.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/XMarkerAlign.java @@ -5,13 +5,11 @@ package com.wordplat.ikvstockchart.align; *

Date: 2017/4/25

* * @author afon + * @since 2021-05-09 */ - public enum XMarkerAlign { - - TOP, - - BOTTOM, - - AUTO, + /** + * TOP BOTTOM AUTO + */ + TOP, BOTTOM, AUTO, } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YLabelAlign.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YLabelAlign.java index 49934bd3195e9090ab6600458bc70bdb394321cc..483216ec463509b924022ff97b756ba49a2127f4 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YLabelAlign.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YLabelAlign.java @@ -5,11 +5,11 @@ package com.wordplat.ikvstockchart.align; *

Date: 2017/4/25

* * @author afon + * @since 2021-05-09 */ - public enum YLabelAlign { - - LEFT, - - RIGHT, + /** + * LEFT RIGHT + */ + LEFT, RIGHT, } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YMarkerAlign.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YMarkerAlign.java index 7b7049f84a710b7edd320521689e4ba924e4959c..e004048e5d59eb59ea29568fd7f57a6c7106dd5b 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YMarkerAlign.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/align/YMarkerAlign.java @@ -5,13 +5,11 @@ package com.wordplat.ikvstockchart.align; *

Date: 2017/4/25

* * @author afon + * @since 2021-05-09 */ - public enum YMarkerAlign { - - LEFT, - - RIGHT, - - AUTO, + /** + * LEFT, RIGHT AUTO + */ + LEFT, RIGHT, AUTO, } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/GestureMoveActionCompat.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/GestureMoveActionCompat.java index d6a0fc90da0ab2a4f444c2b32e2a3b3be3d34376..590c98d91bfc641b1ec60aa7cb91eaef2a91b37a 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/GestureMoveActionCompat.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/GestureMoveActionCompat.java @@ -18,15 +18,16 @@ package com.wordplat.ikvstockchart.compat; -import android.view.MotionEvent; + +import ohos.multimodalinput.event.TouchEvent; /** *

横向移动、垂直移动 识别,解决滑动冲突用的

*

Date: 2017/1/22

* * @author afon + * @since 2021-05-09 */ - public class GestureMoveActionCompat { private OnGestureMoveListener gestureMoveListener; @@ -47,16 +48,16 @@ public class GestureMoveActionCompat { /** * 是否响应点击事件 - * + *

* 因为有手指抖动的影响,有时候会产生少量的 ACTION_MOVE 事件,造成程序识别错误。 - * 如果需要减少识别错误的几率,使用 {@link GestureMoveDetectorCompat} 这个类。 + * 如果需要减少识别错误的几率,使用 {@link com.wordplat.ikvstockchart.detector.GestureDetector} 这个类。 */ private boolean mEnableClick = true; /** * 避免程序识别错误的一个阀值。只有触摸移动的距离大于这个阀值时,才认为是一个有效的移动。 */ - private int touchSlop = 20; + private int touchSlop = 300; private boolean dragging = false; @@ -80,19 +81,18 @@ public class GestureMoveActionCompat { * @param e 事件 e * @param x 本次事件的坐标 x。可以是 e.getRawX() 或是 e.getX(),具体看情况 * @param y 本次事件的坐标 y。可以是 e.getRawY() 或是 e.getY(),具体看情况 - * * @return 事件是否是横向滑动 */ - public boolean onTouchEvent(MotionEvent e, float x, float y) { + public boolean onTouchEvent(TouchEvent e, float x, float y) { switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: + case TouchEvent.PRIMARY_POINT_DOWN: lastMotionY = y; lastMotionX = x; interceptStatus = 0; dragging = false; break; - case MotionEvent.ACTION_MOVE: + case TouchEvent.POINT_MOVE: float deltaY = Math.abs(y - lastMotionY); float deltaX = Math.abs(x - lastMotionX); @@ -120,7 +120,7 @@ public class GestureMoveActionCompat { } break; - case MotionEvent.ACTION_UP: + case TouchEvent.PRIMARY_POINT_UP: if (interceptStatus == 0) { if (mEnableClick && gestureMoveListener != null) { gestureMoveListener.onClick(e, x, y); @@ -129,25 +129,44 @@ public class GestureMoveActionCompat { interceptStatus = 0; dragging = false; break; + default: + break; } return interceptStatus == 2; } + /** + * OnGestureMoveListener + * + * @since 2021-05-09 + */ public interface OnGestureMoveListener { /** * 横向移动 + * + * @param e + * @param x + * @param y */ - void onHorizontalMove(MotionEvent e, float x, float y); + void onHorizontalMove(TouchEvent e, float x, float y); /** * 垂直移动 + * + * @param e + * @param x + * @param y */ - void onVerticalMove(MotionEvent e, float x, float y); + void onVerticalMove(TouchEvent e, float x, float y); /** * 点击事件 + * + * @param e + * @param x + * @param y */ - void onClick(MotionEvent e, float x, float y); + void onClick(TouchEvent e, float x, float y); } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/PerformenceAnalyser.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/PerformenceAnalyser.java index 9321290d6db7f1f00a30786857f2d6de61e64292..7bd5f02828ced8a3fc63d1497d271f7272b8065a 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/PerformenceAnalyser.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/PerformenceAnalyser.java @@ -18,7 +18,8 @@ package com.wordplat.ikvstockchart.compat; -import android.util.Log; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; import java.util.HashMap; import java.util.Map; @@ -31,6 +32,11 @@ import java.util.Map; */ public class PerformenceAnalyser { + /** + * HiLogLabel + */ + static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "PerformenceAnalyser"); + private static final String TAG = "PerformenceAnalyser"; private static volatile PerformenceAnalyser instance; @@ -45,7 +51,7 @@ public class PerformenceAnalyser { analyserCallback = new AnalyserCallback() { @Override public void onWatcherFinish(String key, int item, long ns, long ms) { - Log.d(TAG, "##d onWatcherFinish: " + key + "[" + item + "][" + ms + "ms]"); + HiLog.info(LABEL, "##d onWatcherFinish: " + key + "[" + item + "][" + ms + "ms]"); } }; } @@ -97,20 +103,30 @@ public class PerformenceAnalyser { } } + /** + * CountStops + * + * @since 2021-05-09 + */ private static class CountStops { int stops; long time; } + /** + * AnalyserCallback + * + * @since 2021-05-09 + */ public interface AnalyserCallback { /** * 观察完成 * - * @param key key + * @param key key * @param item 第几项 - * @param ns 纳秒 - * @param ms 毫秒 + * @param ns 纳秒 + * @param ms 毫秒 */ void onWatcherFinish(String key, int item, long ns, long ms); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/ViewUtils.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/ViewUtils.java index efc9bba83580afcd9d9f28fb66172e91dc397f7d..d47ce5e8c5bbb68286e475c8040ede1a6df8a241 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/ViewUtils.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/compat/ViewUtils.java @@ -18,43 +18,50 @@ package com.wordplat.ikvstockchart.compat; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Paint; -import android.text.TextUtils; -import android.util.AttributeSet; -import com.wordplat.ikvstockchart.R; import com.wordplat.ikvstockchart.align.XMarkerAlign; import com.wordplat.ikvstockchart.align.YLabelAlign; import com.wordplat.ikvstockchart.align.YMarkerAlign; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; +import com.wordplat.ikvstockchart.utils.TypedAttrUtils; +import ohos.agp.components.AttrHelper; +import ohos.agp.components.AttrSet; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.app.Context; + +import static ohos.agp.components.AttrHelper.getDensity; /** *

ViewUtils

*

Date: 2017/3/29

* - * @author afon + * @author + * @since 2021-05-09 */ - public class ViewUtils { - /** - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + * 根据手机的分辨率从 vp 的单位 转成为 px(像素) + * + * @param context + * @param vp + * @return int */ - public static int dpTopx(Context context, float dpValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); + public static int vpToPx(final Context context, final float vp) { + return AttrHelper.vp2px(vp, getDensity(context)); } /** - * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + * 根据手机的分辨率从 px(像素) 的单位 转成为 vp + * + * @param context + * @param px + * @return int */ - public static int pxTodp(Context context, float pxValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (pxValue / scale + 0.5f); + public static int pxTovp(final Context context, final float px) { + return Math.round(px / getDensity(context) + 0.5f); } /** @@ -65,40 +72,40 @@ public class ViewUtils { // 设置 涨、跌的颜色 if (entry.getClose() > entry.getOpen()) { // 今日收盘价大于今日开盘价为涨 - candlePaint.setColor(sizeColor.getIncreasingColor()); + candlePaint.setColor(new Color(sizeColor.getIncreasingColor())); } else if (entry.getClose() == entry.getOpen()) { // 今日收盘价等于今日开盘价有涨停、跌停、不涨不跌三种情况 if (currentEntryIndex > 0) { if (entry.getOpen() > entrySet.getEntryList().get(currentEntryIndex - 1).getClose()) { // 今日开盘价大于昨日收盘价为涨停 - candlePaint.setColor(sizeColor.getIncreasingColor()); + candlePaint.setColor(new Color(sizeColor.getIncreasingColor())); } else if (entry.getOpen() == entrySet.getEntryList().get(currentEntryIndex - 1).getClose()) { // 不涨不跌 - candlePaint.setColor(sizeColor.getNeutralColor()); + candlePaint.setColor(new Color(sizeColor.getNeutralColor())); } else { // 否则为跌停 - candlePaint.setColor(sizeColor.getDecreasingColor()); + candlePaint.setColor(new Color(sizeColor.getDecreasingColor())); } } else { if (entry.getOpen() > entrySet.getPreClose()) { - candlePaint.setColor(sizeColor.getIncreasingColor()); + candlePaint.setColor(new Color(sizeColor.getIncreasingColor())); } else if (entry.getOpen() == entrySet.getPreClose()) { - candlePaint.setColor(sizeColor.getNeutralColor()); + candlePaint.setColor(new Color(sizeColor.getNeutralColor())); } else { - candlePaint.setColor(sizeColor.getDecreasingColor()); + candlePaint.setColor(new Color(sizeColor.getDecreasingColor())); } } } else { // 今日收盘价小于今日开盘价为跌 - candlePaint.setColor(sizeColor.getDecreasingColor()); + candlePaint.setColor(new Color(sizeColor.getDecreasingColor())); } - if (candlePaint.getColor() == sizeColor.getIncreasingColor()) { - if (sizeColor.getIncreasingStyle() == Paint.Style.STROKE) { - candlePaint.setStyle(Paint.Style.STROKE); + if (candlePaint.getColor().getValue() == sizeColor.getIncreasingColor()) { + if (sizeColor.getIncreasingStyle() == Paint.Style.STROKE_STYLE) { + candlePaint.setStyle(Paint.Style.STROKE_STYLE); } else { - candlePaint.setStyle(Paint.Style.FILL_AND_STROKE); + candlePaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE); } } else { - if (sizeColor.getDecreasingStyle() == Paint.Style.STROKE) { - candlePaint.setStyle(Paint.Style.STROKE); + if (sizeColor.getDecreasingStyle() == Paint.Style.STROKE_STYLE) { + candlePaint.setStyle(Paint.Style.STROKE_STYLE); } else { - candlePaint.setStyle(Paint.Style.FILL_AND_STROKE); + candlePaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE); } } @@ -108,234 +115,162 @@ public class ViewUtils { /** * 初始化 SizeColor */ - public static SizeColor getSizeColor(Context context, AttributeSet attrs, int defStyleAttr) { - final TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, R.styleable.InteractiveKLineView, defStyleAttr, defStyleAttr); + public static SizeColor getSizeColor(Context context, AttrSet attrs, int defStyleAttr) { final SizeColor sizeColor = new SizeColor(); try { - // 与轴、网格有关的属性 - sizeColor.setXLabelSize(a.getDimension(R.styleable.InteractiveKLineView_xLabelSize, - sizeColor.getXLabelSize())); - - sizeColor.setXLabelColor(a.getColor(R.styleable.InteractiveKLineView_xLabelColor, - sizeColor.getXLabelColor())); - - sizeColor.setXLabelViewHeight(a.getDimension(R.styleable.InteractiveKLineView_xLabelViewHeight, - sizeColor.getXLabelViewHeight())); - - sizeColor.setYLabelSize(a.getDimension(R.styleable.InteractiveKLineView_yLabelSize, - sizeColor.getYLabelSize())); - - sizeColor.setYLabelColor(a.getColor(R.styleable.InteractiveKLineView_yLabelColor, - sizeColor.getYLabelColor())); - - int align = a.getInteger(R.styleable.InteractiveKLineView_yLabelAlign, YLabelAlign.LEFT.ordinal()); - sizeColor.setYLabelAlign(YLabelAlign.values()[align]); - - sizeColor.setAxisSize(a.getDimension(R.styleable.InteractiveKLineView_axisSize, - sizeColor.getAxisSize())); - - sizeColor.setAxisColor(a.getColor(R.styleable.InteractiveKLineView_axisColor, - sizeColor.getAxisColor())); - - sizeColor.setGridSize(a.getDimension(R.styleable.InteractiveKLineView_gridSize, - sizeColor.getGridSize())); - - sizeColor.setGridColor(a.getColor(R.styleable.InteractiveKLineView_gridColor, - sizeColor.getGridColor())); - - // 与高亮、MarkerView 有关的属性 - sizeColor.setHighlightSize(a.getDimension(R.styleable.InteractiveKLineView_highlightSize, - sizeColor.getHighlightSize())); - - sizeColor.setHighlightColor(a.getColor(R.styleable.InteractiveKLineView_highlightColor, - sizeColor.getHighlightColor())); - - sizeColor.setMarkerBorderSize(a.getDimension(R.styleable.InteractiveKLineView_markerBorderSize, - sizeColor.getMarkerBorderSize())); - - sizeColor.setMarkerBorderColor(a.getColor(R.styleable.InteractiveKLineView_markerBorderColor, - sizeColor.getMarkerBorderColor())); - - sizeColor.setMarkerTextSize(a.getDimension(R.styleable.InteractiveKLineView_markerTextSize, - sizeColor.getMarkerTextSize())); - - sizeColor.setMarkerTextColor(a.getColor(R.styleable.InteractiveKLineView_markerTextColor, - sizeColor.getMarkerTextColor())); - - align = a.getInteger(R.styleable.InteractiveKLineView_xMarkerAlign, XMarkerAlign.AUTO.ordinal()); - sizeColor.setXMarkerAlign(XMarkerAlign.values()[align]); - - align = a.getInteger(R.styleable.InteractiveKLineView_yMarkerAlign, YMarkerAlign.AUTO.ordinal()); - sizeColor.setYMarkerAlign(YMarkerAlign.values()[align]); - - // 与分时图有关的属性 - sizeColor.setTimeLineSize(a.getDimension(R.styleable.InteractiveKLineView_timeLineSize, - sizeColor.getTimeLineSize())); - - sizeColor.setTimeLineColor(a.getColor(R.styleable.InteractiveKLineView_timeLineColor, - sizeColor.getTimeLineColor())); - - sizeColor.setTimeLineMaxCount(a.getInteger(R.styleable.InteractiveKLineView_timeLineMaxCount, - sizeColor.getTimeLineMaxCount())); - - // 与蜡烛图有关的属性 - sizeColor.setCandleBorderSize(a.getDimension(R.styleable.InteractiveKLineView_candleBorderSize, - sizeColor.getCandleBorderSize())); - - sizeColor.setCandleExtremumLabelSize(a.getDimension(R.styleable.InteractiveKLineView_candleExtremumLabelSize, - sizeColor.getCandleExtremumLabelSize())); - - sizeColor.setCandleExtremumLableColor(a.getColor(R.styleable.InteractiveKLineView_candleExtremumLableColor, - sizeColor.getCandleExtremumLableColor())); - - sizeColor.setShadowSize(a.getDimension(R.styleable.InteractiveKLineView_shadowSize, - sizeColor.getShadowSize())); - - sizeColor.setIncreasingColor(a.getColor(R.styleable.InteractiveKLineView_increasingColor, - sizeColor.getIncreasingColor())); - - sizeColor.setDecreasingColor(a.getColor(R.styleable.InteractiveKLineView_decreasingColor, - sizeColor.getDecreasingColor())); - - sizeColor.setNeutralColor(a.getColor(R.styleable.InteractiveKLineView_neutralColor, - sizeColor.getNeutralColor())); - - sizeColor.setPortraitDefaultVisibleCount(a.getInteger(R.styleable.InteractiveKLineView_portraitDefaultVisibleCount, - sizeColor.getPortraitDefaultVisibleCount())); - - sizeColor.setZoomInTimes(a.getInteger(R.styleable.InteractiveKLineView_zoomInTimes, - sizeColor.getZoomInTimes())); - - sizeColor.setZoomOutTimes(a.getInteger(R.styleable.InteractiveKLineView_zoomOutTimes, - sizeColor.getZoomOutTimes())); - - int style = a.getInteger(R.styleable.InteractiveKLineView_increasingStyle, Paint.Style.FILL.ordinal()); - sizeColor.setIncreasingStyle(Paint.Style.values()[style]); - - style = a.getInteger(R.styleable.InteractiveKLineView_decreasingStyle, Paint.Style.FILL.ordinal()); - sizeColor.setDecreasingStyle(Paint.Style.values()[style]); - - // 与股票指标有关的属性 - sizeColor.setMaLineSize(a.getDimension(R.styleable.InteractiveKLineView_maLineSize, - sizeColor.getMaLineSize())); - - sizeColor.setMa5Color(a.getColor(R.styleable.InteractiveKLineView_ma5Color, - sizeColor.getMa5Color())); - - sizeColor.setMa10Color(a.getColor(R.styleable.InteractiveKLineView_ma10Color, - sizeColor.getMa10Color())); - - sizeColor.setMa20Color(a.getColor(R.styleable.InteractiveKLineView_ma20Color, - sizeColor.getMa20Color())); - - sizeColor.setBollLineSize(a.getDimension(R.styleable.InteractiveKLineView_bollLineSize, - sizeColor.getBollLineSize())); - - sizeColor.setBollMidLineColor(a.getColor(R.styleable.InteractiveKLineView_bollMidLineColor, - sizeColor.getBollMidLineColor())); - - sizeColor.setBollUpperLineColor(a.getColor(R.styleable.InteractiveKLineView_bollUpperLineColor, - sizeColor.getBollUpperLineColor())); - - sizeColor.setBollLowerLineColor(a.getColor(R.styleable.InteractiveKLineView_bollLowerLineColor, - sizeColor.getBollLowerLineColor())); - - sizeColor.setKdjLineSize(a.getDimension(R.styleable.InteractiveKLineView_kdjLineSize, - sizeColor.getKdjLineSize())); - - sizeColor.setKdjKLineColor(a.getColor(R.styleable.InteractiveKLineView_kdjKLineColor, - sizeColor.getKdjKLineColor())); - - sizeColor.setKdjDLineColor(a.getColor(R.styleable.InteractiveKLineView_kdjDLineColor, - sizeColor.getKdjDLineColor())); - - sizeColor.setKdjJLineColor(a.getColor(R.styleable.InteractiveKLineView_kdjJLineColor, - sizeColor.getKdjJLineColor())); - - sizeColor.setMacdLineSize(a.getDimension(R.styleable.InteractiveKLineView_macdLineSize, - sizeColor.getMacdLineSize())); - - sizeColor.setMacdHighlightTextColor(a.getColor(R.styleable.InteractiveKLineView_macdHighlightTextColor, - sizeColor.getMacdHighlightTextColor())); - - sizeColor.setDeaLineColor(a.getColor(R.styleable.InteractiveKLineView_deaLineColor, - sizeColor.getDeaLineColor())); - - sizeColor.setDiffLineColor(a.getColor(R.styleable.InteractiveKLineView_diffLineColor, - sizeColor.getDiffLineColor())); - - sizeColor.setRsiLineSize(a.getDimension(R.styleable.InteractiveKLineView_rsiLineSize, - sizeColor.getRsiLineSize())); - - sizeColor.setRsi1LineColor(a.getColor(R.styleable.InteractiveKLineView_rsi1LineColor, - sizeColor.getRsi1LineColor())); - - sizeColor.setRsi2LineColor(a.getColor(R.styleable.InteractiveKLineView_rsi2LineColor, - sizeColor.getRsi2LineColor())); - - sizeColor.setRsi3LineColor(a.getColor(R.styleable.InteractiveKLineView_rsi3LineColor, - sizeColor.getRsi3LineColor())); - - sizeColor.setMaTextSize(a.getDimension(R.styleable.InteractiveKLineView_maTextSize, - sizeColor.getMaTextSize())); - - sizeColor.setMaTextColor(a.getColor(R.styleable.InteractiveKLineView_maTextColor, - sizeColor.getMaTextColor())); - - sizeColor.setBollTextSize(a.getDimension(R.styleable.InteractiveKLineView_bollTextSize, - sizeColor.getBollTextSize())); - - sizeColor.setBollTextColor(a.getColor(R.styleable.InteractiveKLineView_bollTextColor, - sizeColor.getBollTextColor())); - - sizeColor.setKdjTextSize(a.getDimension(R.styleable.InteractiveKLineView_kdjTextSize, - sizeColor.getKdjTextSize())); - - sizeColor.setKdjTextColor(a.getColor(R.styleable.InteractiveKLineView_kdjTextColor, - sizeColor.getKdjTextColor())); - - sizeColor.setMacdTextSize(a.getDimension(R.styleable.InteractiveKLineView_macdTextSize, - sizeColor.getMacdTextSize())); - - sizeColor.setMacdTextColor(a.getColor(R.styleable.InteractiveKLineView_macdTextColor, - sizeColor.getMacdTextColor())); - - sizeColor.setRsiTextSize(a.getDimension(R.styleable.InteractiveKLineView_rsiTextSize, - sizeColor.getRsiTextSize())); - - sizeColor.setRsiTextColor(a.getColor(R.styleable.InteractiveKLineView_rsiTextColor, - sizeColor.getRsiTextColor())); - - // 其它 - sizeColor.setLoadingTextSize(a.getDimension(R.styleable.InteractiveKLineView_loadingTextSize, - sizeColor.getLoadingTextSize())); + /** + * 与轴、网格有关的属性 + */ + sizeColor.setXLabelSize(TypedAttrUtils.getFloat(attrs, "xLabelSize", sizeColor.getXLabelSize())); + sizeColor.setXLabelColor(TypedAttrUtils.getIntColor(attrs, "xLabelColor", sizeColor.getXLabelColor())); + sizeColor.setXLabelViewHeight(TypedAttrUtils.getFloat(attrs, "xLabelViewHeight", sizeColor.getXLabelViewHeight())); + sizeColor.setYLabelSize(TypedAttrUtils.getFloat(attrs, "yLabelSize", sizeColor.getYLabelSize())); + sizeColor.setYLabelColor(TypedAttrUtils.getIntColor(attrs, "yLabelColor", sizeColor.getYLabelColor())); + + String align = TypedAttrUtils.getString(attrs, "yLabelAlign", YLabelAlign.LEFT.toString()); + align = align.toUpperCase(); + int alignIndex = 0; + if (align.equals(YLabelAlign.LEFT.toString())) { + alignIndex = 0; + } else if (align.equals(YLabelAlign.RIGHT.toString())) { + alignIndex = 1; + } - sizeColor.setLoadingTextColor(a.getColor(R.styleable.InteractiveKLineView_loadingTextColor, - sizeColor.getLoadingTextColor())); + sizeColor.setYLabelAlign(YLabelAlign.values()[alignIndex]); + sizeColor.setAxisSize(TypedAttrUtils.getFloat(attrs, "axisSize", sizeColor.getAxisSize())); + sizeColor.setAxisColor(TypedAttrUtils.getIntColor(attrs, "axisColor", sizeColor.getAxisColor())); + sizeColor.setGridSize(TypedAttrUtils.getFloat(attrs, "gridSize", sizeColor.getGridSize())); + sizeColor.setGridColor(TypedAttrUtils.getIntColor(attrs, "gridColor", sizeColor.getGridColor())); + + /** + * 与高亮、MarkerView 有关的属性 + */ + sizeColor.setHighlightSize(TypedAttrUtils.getFloat(attrs, "highlightSize", sizeColor.getHighlightSize())); + sizeColor.setHighlightColor(TypedAttrUtils.getIntColor(attrs, "highlightColor", sizeColor.getHighlightColor())); + sizeColor.setMarkerBorderSize(TypedAttrUtils.getFloat(attrs, "markerBorderSize", sizeColor.getMarkerBorderSize())); + sizeColor.setMarkerBorderColor(TypedAttrUtils.getIntColor(attrs, "markerBorderColor", sizeColor.getMarkerBorderColor())); + sizeColor.setMarkerTextSize(TypedAttrUtils.getFloat(attrs, "markerTextSize", sizeColor.getMarkerTextSize())); + sizeColor.setMarkerTextColor(TypedAttrUtils.getIntColor(attrs, "markerTextColor", sizeColor.getMarkerTextColor())); + + align = TypedAttrUtils.getString(attrs, "xMarkerAlign", XMarkerAlign.AUTO.toString()); + align = align.toUpperCase(); + if (align.equals(XMarkerAlign.TOP.toString())) { + alignIndex = 0; + } else if (align.equals(XMarkerAlign.BOTTOM.toString())) { + alignIndex = 1; + } else if (align.equals(XMarkerAlign.AUTO.toString())) { + alignIndex = 2; + } + sizeColor.setXMarkerAlign(XMarkerAlign.values()[alignIndex]); + + align = TypedAttrUtils.getString(attrs, "yMarkerAlign", YMarkerAlign.AUTO.toString()); + align = align.toUpperCase(); + if (align.equals(YMarkerAlign.LEFT.toString())) { + alignIndex = 0; + } else if (align.equals(YMarkerAlign.RIGHT.toString())) { + alignIndex = 1; + } else if (align.equals(YMarkerAlign.AUTO.toString())) { + alignIndex = 2; + } + sizeColor.setYMarkerAlign(YMarkerAlign.values()[alignIndex]); + + /** + * 与分时图有关的属性 + */ + sizeColor.setTimeLineSize(TypedAttrUtils.getFloat(attrs, "timeLineSize", sizeColor.getTimeLineSize())); + sizeColor.setTimeLineColor(TypedAttrUtils.getIntColor(attrs, "timeLineColor", sizeColor.getTimeLineColor())); + sizeColor.setTimeLineMaxCount(TypedAttrUtils.getInteger(attrs, "timeLineMaxCount", sizeColor.getTimeLineMaxCount())); + + /** + * 与蜡烛图有关的属性 + */ + sizeColor.setCandleBorderSize(TypedAttrUtils.getFloat(attrs, "candleBorderSize", sizeColor.getCandleBorderSize())); + sizeColor.setCandleExtremumLabelSize(TypedAttrUtils.getFloat(attrs, "candleExtremumLabelSize", sizeColor.getCandleExtremumLabelSize())); + sizeColor.setCandleExtremumLableColor(TypedAttrUtils.getIntColor(attrs, "candleExtremumLableColor", sizeColor.getCandleExtremumLableColor())); + sizeColor.setShadowSize(TypedAttrUtils.getFloat(attrs, "shadowSize", sizeColor.getShadowSize())); + sizeColor.setIncreasingColor(TypedAttrUtils.getIntColor(attrs, "increasingColor", sizeColor.getIncreasingColor())); + sizeColor.setDecreasingColor(TypedAttrUtils.getIntColor(attrs, "decreasingColor", sizeColor.getDecreasingColor())); + sizeColor.setNeutralColor(TypedAttrUtils.getIntColor(attrs, "neutralColor", sizeColor.getNeutralColor())); + sizeColor.setPortraitDefaultVisibleCount(TypedAttrUtils.getInteger(attrs, "portraitDefaultVisibleCount", sizeColor.getPortraitDefaultVisibleCount())); + sizeColor.setZoomInTimes(TypedAttrUtils.getInteger(attrs, "zoomInTimes", sizeColor.getZoomInTimes())); + sizeColor.setZoomOutTimes(TypedAttrUtils.getInteger(attrs, "zoomOutTimes", sizeColor.getZoomOutTimes())); + + align = TypedAttrUtils.getString(attrs, "increasingStyle", "fill"); + if ("fill".equals(align)) { + alignIndex = 0; + } else if ("stroke".equals(align)) { + alignIndex = 1; + } + sizeColor.setIncreasingStyle(Paint.Style.values()[alignIndex]); - String loadingText = a.getString(R.styleable.InteractiveKLineView_loadingText); - if (!TextUtils.isEmpty(loadingText)) { + align = TypedAttrUtils.getString(attrs, "decreasingStyle", "fill"); + if ("fill".equals(align)) { + alignIndex = 0; + } else if ("stroke".equals(align)) { + alignIndex = 1; + } + sizeColor.setDecreasingStyle(Paint.Style.values()[alignIndex]); + + /** + * 与股票指标有关的属性 + */ + sizeColor.setMaLineSize(TypedAttrUtils.getFloat(attrs, "maLineSize", sizeColor.getMaLineSize())); + sizeColor.setMa5Color(TypedAttrUtils.getIntColor(attrs, "ma5Color", sizeColor.getMa5Color())); + sizeColor.setMa10Color(TypedAttrUtils.getIntColor(attrs, "ma10Color", sizeColor.getMa10Color())); + sizeColor.setMa20Color(TypedAttrUtils.getIntColor(attrs, "ma20Color", sizeColor.getMa20Color())); + sizeColor.setBollLineSize(TypedAttrUtils.getFloat(attrs, "bollLineSize", sizeColor.getBollLineSize())); + sizeColor.setBollMidLineColor(TypedAttrUtils.getIntColor(attrs, "bollMidLineColor", sizeColor.getBollMidLineColor())); + sizeColor.setBollUpperLineColor(TypedAttrUtils.getIntColor(attrs, "bollUpperLineColor", sizeColor.getBollUpperLineColor())); + sizeColor.setBollLowerLineColor(TypedAttrUtils.getIntColor(attrs, "bollLowerLineColor", sizeColor.getBollLowerLineColor())); + sizeColor.setKdjLineSize(TypedAttrUtils.getFloat(attrs, "kdjLineSize", sizeColor.getKdjLineSize())); + sizeColor.setKdjKLineColor(TypedAttrUtils.getIntColor(attrs, "kdjKLineColor", sizeColor.getKdjKLineColor())); + sizeColor.setKdjDLineColor(TypedAttrUtils.getIntColor(attrs, "kdjDLineColor", sizeColor.getKdjDLineColor())); + sizeColor.setKdjJLineColor(TypedAttrUtils.getIntColor(attrs, "kdjJLineColor", sizeColor.getKdjJLineColor())); + sizeColor.setMacdLineSize(TypedAttrUtils.getFloat(attrs, "macdLineSize", sizeColor.getMacdLineSize())); + sizeColor.setMacdHighlightTextColor(TypedAttrUtils.getIntColor(attrs, "macdHighlightTextColor", sizeColor.getMacdHighlightTextColor())); + sizeColor.setDeaLineColor(TypedAttrUtils.getIntColor(attrs, "deaLineColor", sizeColor.getDeaLineColor())); + sizeColor.setDiffLineColor(TypedAttrUtils.getIntColor(attrs, "diffLineColor", sizeColor.getDiffLineColor())); + sizeColor.setRsiLineSize(TypedAttrUtils.getFloat(attrs, "rsiLineSize", sizeColor.getRsiLineSize())); + sizeColor.setRsi1LineColor(TypedAttrUtils.getIntColor(attrs, "rsi1LineColor", sizeColor.getRsi1LineColor())); + sizeColor.setRsi2LineColor(TypedAttrUtils.getIntColor(attrs, "rsi2LineColor", sizeColor.getRsi2LineColor())); + sizeColor.setRsi3LineColor(TypedAttrUtils.getIntColor(attrs, "rsi3LineColor", sizeColor.getRsi3LineColor())); + + sizeColor.setMaTextSize(TypedAttrUtils.getFloat(attrs, "maTextSize", sizeColor.getMaTextSize())); + sizeColor.setMaTextColor(TypedAttrUtils.getIntColor(attrs, "maTextColor", sizeColor.getMaTextColor())); + + sizeColor.setBollTextSize(TypedAttrUtils.getFloat(attrs, "bollTextSize", sizeColor.getBollTextSize())); + sizeColor.setBollTextColor(TypedAttrUtils.getIntColor(attrs, "bollTextColor", sizeColor.getBollTextColor())); + + sizeColor.setKdjTextSize(TypedAttrUtils.getFloat(attrs, "kdjTextSize", sizeColor.getKdjTextSize())); + sizeColor.setKdjTextColor(TypedAttrUtils.getIntColor(attrs, "kdjTextColor", sizeColor.getKdjTextColor())); + + sizeColor.setMacdTextSize(TypedAttrUtils.getFloat(attrs, "macdTextSize", sizeColor.getMacdTextSize())); + sizeColor.setMacdTextColor(TypedAttrUtils.getIntColor(attrs, "macdTextColor", sizeColor.getMacdTextColor())); + + sizeColor.setRsiTextSize(TypedAttrUtils.getFloat(attrs, "rsiTextSize", sizeColor.getRsiTextSize())); + sizeColor.setRsiTextColor(TypedAttrUtils.getIntColor(attrs, "rsiTextColor", sizeColor.getRsiTextColor())); + + /** + * 其它 + */ + sizeColor.setLoadingTextSize(TypedAttrUtils.getFloat(attrs, "loadingTextSize", sizeColor.getLoadingTextSize())); + sizeColor.setLoadingTextColor(TypedAttrUtils.getIntColor(attrs, "loadingTextColor", sizeColor.getLoadingTextColor())); + String loadingText = TypedAttrUtils.getString(attrs, "loadingText", ""); + if (!"".equals(loadingText)) { sizeColor.setLoadingText(loadingText); } - sizeColor.setErrorTextSize(a.getDimension(R.styleable.InteractiveKLineView_errorTextSize, - sizeColor.getErrorTextSize())); - - sizeColor.setErrorTextColor(a.getColor(R.styleable.InteractiveKLineView_errorTextColor, - sizeColor.getErrorTextColor())); - - String errorText = a.getString(R.styleable.InteractiveKLineView_errorText); - if (!TextUtils.isEmpty(errorText)) { + sizeColor.setErrorTextSize(TypedAttrUtils.getFloat(attrs, "errorTextSize", sizeColor.getErrorTextSize())); + sizeColor.setErrorTextColor(TypedAttrUtils.getIntColor(attrs, "errorTextColor", sizeColor.getErrorTextColor())); + String errorText = TypedAttrUtils.getString(attrs, "errorText", ""); + if (!"".equals(errorText)) { sizeColor.setErrorText(errorText); } } finally { - a.recycle(); } - return sizeColor; } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/GestureDetector.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/GestureDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..e50b8caed85b40d8c6b28b5a2cdad01892e09501 --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/GestureDetector.java @@ -0,0 +1,675 @@ +package com.wordplat.ikvstockchart.detector; + +import ohos.app.Context; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; +import ohos.eventhandler.InnerEvent; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.MmiPoint; +import ohos.multimodalinput.event.TouchEvent; + +/** + * GestureDetector + * + * @since 2021-05-09 + */ +public class GestureDetector { + /** + * HiLogLabel + */ + static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "GestureDetector"); + + /** + * GestureDetector + * + * @since 2021-05-09 + */ + public interface OnGestureListener { + /** + * onDown + * + * @param e + * @return boolean + */ + boolean onDown(TouchEvent e); + + /** + * onShowPress + * + * @param e + */ + void onShowPress(TouchEvent e); + + /** + * onSingleTapUp + * + * @param e + * @return boolean + */ + boolean onSingleTapUp(TouchEvent e); + + /** + * onScroll + * + * @param e1 + * @param e2 + * @param distanceX + * @param distanceY + * @return boolean + */ + boolean onScroll(TouchEvent e1, TouchEvent e2, float distanceX, float distanceY); + + /** + * onLongPress + * + * @param e + */ + void onLongPress(TouchEvent e); + + /** + * onFling + * + * @param e1 + * @param e2 + * @param velocityX + * @param velocityY + * @param scrollY0 + * @param scrollY1 + * @return boolean + */ + boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, float velocityY, float scrollY0, float scrollY1); + } + + /** + * OnDoubleTapListener + * + * @since 2021-05-09 + */ + public interface OnDoubleTapListener { + /** + * onSingleTapConfirmed + * + * @param e + * @return boolean + */ + boolean onSingleTapConfirmed(TouchEvent e); + + /** + * onDoubleTap + * + * @param e + * @return boolean + */ + boolean onDoubleTap(TouchEvent e); + + /** + * onDoubleTapEvent + * + * @param e + * @return boolean + */ + boolean onDoubleTapEvent(TouchEvent e); + } + + /** + * OnContextClickListener + * + * @since 2021-05-09 + */ + public interface OnContextClickListener { + /** + * onContextClick + * + * @param e + * @return boolean + */ + boolean onContextClick(TouchEvent e); + } + + /** + * SimpleOnGestureListener + * + * @since 2021-05-09 + */ + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener, + OnContextClickListener { + /** + * onSingleTapUp + * + * @param e + * @return boolean + */ + public boolean onSingleTapUp(TouchEvent e) { + return false; + } + + /** + * onLongPress + * + * @param e + */ + public void onLongPress(TouchEvent e) { + } + + /** + * onScroll + * + * @param e1 + * @param e2 + * @param distanceX + * @param distanceY + * @return boolean + */ + public boolean onScroll(TouchEvent e1, TouchEvent e2, + float distanceX, float distanceY) { + return false; + } + + /** + * onFling + * + * @param e1 + * @param e2 + * @param velocityX + * @param velocityY + * @return boolean + */ + public boolean onFling(TouchEvent e1, TouchEvent e2, float velocityX, + float velocityY, float scrollY0, float scrollY1) { + return false; + } + + /** + * onShowPress + * + * @param e + */ + public void onShowPress(TouchEvent e) { + } + + /** + * onDown + * + * @param e + * @return boolean + */ + public boolean onDown(TouchEvent e) { + return false; + } + + /** + * onDoubleTap + * + * @param e + * @return boolean + */ + public boolean onDoubleTap(TouchEvent e) { + return false; + } + + /** + * onDoubleTapEvent + * + * @param e + * @return boolean + */ + public boolean onDoubleTapEvent(TouchEvent e) { + return false; + } + + /** + * onSingleTapConfirmed + * + * @param e + * @return boolean + */ + public boolean onSingleTapConfirmed(TouchEvent e) { + return false; + } + + /** + * onContextClick + * + * @param e + * @return boolean + */ + public boolean onContextClick(TouchEvent e) { + return false; + } + } + + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); + private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); + private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime(); + + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + private static final int TAP = 3; + private int mTouchSlopSquare; + private int mDoubleTapTouchSlopSquare; + private int mDoubleTapSlopSquare; + private int mMinimumFlingVelocity; + private int mMaximumFlingVelocity; + + + private final EventHandler mHandler; + private final OnGestureListener mListener; + private OnDoubleTapListener mDoubleTapListener; + private OnContextClickListener mContextClickListener; + + private boolean mStillDown; + private boolean mDeferConfirmSingleTap; + private boolean mInLongPress; + private boolean mInContextClick; + private boolean mAlwaysInTapRegion; + private boolean mAlwaysInBiggerTapRegion; + private boolean mIgnoreNextUpEvent; + + private TouchEvent mCurrentDownEvent; + private TouchEvent mPreviousUpEvent; + private long mPreviousUpOccuredTime; + + private boolean mIsDoubleTapping; + + private float mLastFocusX; + private float mLastFocusY; + private float mDownFocusX; + private float mDownFocusY; + + private boolean mIsLongpressEnabled; + + private VelocityTracker mVelocityTracker; + + private class GestureHandler extends EventHandler { + GestureHandler() { + super(EventRunner.getMainEventRunner()); + } + + GestureHandler(EventHandler handler) { + super(handler.getEventRunner()); + } + + @Override + protected void processEvent(InnerEvent event) { + switch (event.eventId) { + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null) { + if (!mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } else { + mDeferConfirmSingleTap = true; + } + } + break; + + default: + throw new RuntimeException("Unknown message " + event); //never + } + } + } + + /** + * GestureDetector + * + * @param listener + * @param handler + */ + public GestureDetector(OnGestureListener listener, EventHandler handler) { + this(null, listener, handler); + } + + /** + * GestureDetector + * + * @param listener + */ + public GestureDetector(OnGestureListener listener) { + this(null, listener, null); + } + + /** + * GestureDetector + * + * @param context + * @param listener + */ + public GestureDetector(Context context, OnGestureListener listener) { + this(context, listener, null); + } + + /** + * GestureDetector + * + * @param context + * @param listener + * @param handler + */ + public GestureDetector(Context context, OnGestureListener listener, EventHandler handler) { + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + if (listener instanceof OnContextClickListener) { + setContextClickListener((OnContextClickListener) listener); + } + init(context); + } + + /** + * GestureDetector + * + * @param context + * @param listener + * @param handler + * @param unused + */ + public GestureDetector(Context context, OnGestureListener listener, EventHandler handler, + boolean unused) { + this(context, listener, handler); + } + + private void init(Context context) { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + + // Fallback to support pre-donuts releases + int touchSlop, doubleTapSlop, doubleTapTouchSlop; + //noinspection deprecation + touchSlop = ViewConfiguration.getTouchSlop(); + doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this + doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); + //noinspection deprecation + mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); + mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); + + mTouchSlopSquare = touchSlop * touchSlop; + mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; + mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + } + + public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { + mDoubleTapListener = onDoubleTapListener; + } + + public void setContextClickListener(OnContextClickListener onContextClickListener) { + mContextClickListener = onContextClickListener; + } + + public void setIsLongpressEnabled(boolean isLongpressEnabled) { + mIsLongpressEnabled = isLongpressEnabled; + } + + public boolean isLongpressEnabled() { + return mIsLongpressEnabled; + } + + /** + * onTouchEvent + * + * @param ev + * @return boolean + */ + public boolean onTouchEvent(TouchEvent ev) { + + final int action = ev.getAction(); + + if (mVelocityTracker == null) { + mVelocityTracker = new VelocityTracker(); + } + mVelocityTracker.addMovement(ev); + + final boolean pointerUp = action == TouchEvent.OTHER_POINT_UP; + final int skipIndex = pointerUp ? ev.getIndex() : -1; + final boolean isGeneratedGesture = false; + + // Determine focal point + float sumX = 0, sumY = 0; + final int count = ev.getPointerCount(); + for (int i = 0; i < count; i++) { + if (skipIndex == i) { + continue; + } + MmiPoint point = ev.getPointerPosition(i); + sumX += point.getX(); + sumY += point.getY(); + } + final int div = pointerUp ? count - 1 : count; + final float focusX = sumX / div; + final float focusY = sumY / div; + + boolean handled = false; + + switch (action) { + case TouchEvent.OTHER_POINT_DOWN: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + // Cancel long press and taps + cancelTaps(); + break; + + case TouchEvent.OTHER_POINT_UP: + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + mVelocityTracker.calculateCurrentVelocity(1000, mMaximumFlingVelocity); + final int upIndex = ev.getIndex(); + final int id1 = ev.getPointerId(upIndex); + final float x1 = mVelocityTracker.getXVelocity(id1); + final float y1 = mVelocityTracker.getYVelocity(id1); + for (int i = 0; i < count; i++) { + if (i == upIndex) { + continue; + } + + final int id2 = ev.getPointerId(i); + final float x = x1 * mVelocityTracker.getXVelocity(id2); + final float y = y1 * mVelocityTracker.getYVelocity(id2); + + final float dot = x + y; + if (dot < 0) { + mVelocityTracker.clear(); + break; + } + } + + break; + + case TouchEvent.PRIMARY_POINT_DOWN: + if (mDoubleTapListener != null) { + boolean hadTapMessage = mHandler.hasInnerEvent(TAP); + if (hadTapMessage) mHandler.removeEvent(TAP); + if ((ev != null) && (mPreviousUpEvent != null) + && hadTapMessage + && isConsideredDoubleTap(ev, mPreviousUpEvent, ev)) { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else { + // This is a first tap + // EventHandler 的hasInnerEvent只能通过param过滤 不能通过eventId过滤 所以得设置param + InnerEvent tapEvent = InnerEvent.get(TAP, TAP); + mHandler.sendEvent(tapEvent, DOUBLE_TAP_TIMEOUT, EventHandler.Priority.IMMEDIATE); + } + } + + mDownFocusX = mLastFocusX = focusX; + mDownFocusY = mLastFocusY = focusY; + if (mCurrentDownEvent != null) { + } + mCurrentDownEvent = ev; + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + mDeferConfirmSingleTap = false; + + if (mIsLongpressEnabled) { + + mHandler.removeEvent(LONG_PRESS); + mHandler.sendTimingEvent(LONG_PRESS, mCurrentDownEvent.getStartTime() + LONGPRESS_TIMEOUT, EventHandler.Priority.IMMEDIATE); + } + mHandler.sendTimingEvent(SHOW_PRESS, + mCurrentDownEvent.getStartTime() + TAP_TIMEOUT, EventHandler.Priority.IMMEDIATE); + handled |= mListener.onDown(ev); + break; + + case TouchEvent.POINT_MOVE: + if (mInLongPress || mInContextClick) { + break; + } + final float scrollX = mLastFocusX - focusX; + final float scrollY = mLastFocusY - focusY; + if (mIsDoubleTapping) { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mAlwaysInTapRegion) { + final int deltaX = (int) (focusX - mDownFocusX); + final int deltaY = (int) (focusY - mDownFocusY); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare; + if (distance > slopSquare) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastFocusX = focusX; + mLastFocusY = focusY; + mAlwaysInTapRegion = false; + mHandler.removeEvent(TAP); + mHandler.removeEvent(SHOW_PRESS); + mHandler.removeEvent(LONG_PRESS); + } + int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare; + if (distance > doubleTapSlopSquare) { + mAlwaysInBiggerTapRegion = false; + } + } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastFocusX = focusX; + mLastFocusY = focusY; + } + break; + + case TouchEvent.PRIMARY_POINT_UP: + mStillDown = false; + + TouchEvent currentUpEvent = ev; + if (mIsDoubleTapping) { + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mInLongPress) { + mHandler.removeEvent(TAP); + mInLongPress = false; + } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + handled = mListener.onSingleTapUp(ev); + if (mDeferConfirmSingleTap && mDoubleTapListener != null) { + mDoubleTapListener.onSingleTapConfirmed(ev); + } + } else if (!mIgnoreNextUpEvent) { + + final VelocityTracker velocityTracker = mVelocityTracker; + final int pointerId = ev.getPointerId(0); + velocityTracker.calculateCurrentVelocity(1000, mMaximumFlingVelocity); + final float velocityY = velocityTracker.getYVelocity(pointerId); + final float velocityX = velocityTracker.getXVelocity(pointerId); + + if ((Math.abs(velocityY) > mMinimumFlingVelocity) + || (Math.abs(velocityX) > mMinimumFlingVelocity)) { + handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY, mDownFocusY, mLastFocusY); + } + } + mPreviousUpEvent = currentUpEvent; + mPreviousUpOccuredTime = currentUpEvent.getOccurredTime(); + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + mIsDoubleTapping = false; + mDeferConfirmSingleTap = false; + mIgnoreNextUpEvent = false; + mHandler.removeEvent(SHOW_PRESS); + mHandler.removeEvent(LONG_PRESS); + break; + + case TouchEvent.CANCEL: + cancel(); + break; + } + return handled; + } + + private void cancel() { + mHandler.removeEvent(SHOW_PRESS); + mHandler.removeEvent(LONG_PRESS); + mHandler.removeEvent(TAP); + mVelocityTracker.recycle(); + mVelocityTracker = null; + mIsDoubleTapping = false; + mStillDown = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + mDeferConfirmSingleTap = false; + mInLongPress = false; + mInContextClick = false; + mIgnoreNextUpEvent = false; + } + + private void cancelTaps() { + mHandler.removeEvent(SHOW_PRESS); + mHandler.removeEvent(LONG_PRESS); + mHandler.removeEvent(TAP); + mIsDoubleTapping = false; + mAlwaysInTapRegion = false; + mAlwaysInBiggerTapRegion = false; + mDeferConfirmSingleTap = false; + mInLongPress = false; + mInContextClick = false; + mIgnoreNextUpEvent = false; + } + + private boolean isConsideredDoubleTap(TouchEvent firstDown, TouchEvent firstUp, + TouchEvent secondDown) { + if (!mAlwaysInBiggerTapRegion) { + return false; + } + + final long deltaTime = secondDown.getOccurredTime() - mPreviousUpOccuredTime; + if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) { + return false; + } + + MmiPoint firstDownPoint = firstDown.getPointerPosition(firstDown.getIndex()); + MmiPoint secondDownPoint = secondDown.getPointerPosition(secondDown.getIndex()); + int deltaX = (int) firstDownPoint.getX() - (int) secondDownPoint.getX(); + int deltaY = (int) firstDownPoint.getY() - (int) secondDownPoint.getY(); + int slopSquare = mDoubleTapSlopSquare; + return (deltaX * deltaX + deltaY * deltaY < slopSquare); + } + + private void dispatchLongPress() { + mHandler.removeEvent(TAP); + mDeferConfirmSingleTap = false; + mInLongPress = true; + mListener.onLongPress(mCurrentDownEvent); + } +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ScaleGestureDetector.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ScaleGestureDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..7b0c20263cc298037502fbe780f1133d044c4154 --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ScaleGestureDetector.java @@ -0,0 +1,538 @@ +package com.wordplat.ikvstockchart.detector; + +import ohos.app.Context; +import ohos.eventhandler.EventHandler; +import ohos.multimodalinput.event.MmiPoint; +import ohos.multimodalinput.event.TouchEvent; + +/** + * \ + * ScaleGestureDetector + * + * @since 2021-05-09 + */ +public class ScaleGestureDetector { + private static final String TAG = "ScaleGestureDetector"; + + /** + * The listener for receiving notifications when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnScaleGestureListener}. + *

+ * An application will receive events in the following order: + *

    + *
  • One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} + *
  • Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} + *
  • One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)} + *
+ */ + public interface OnScaleGestureListener { + + /** + * Responds to scaling events for a gesture in progress. + * Reported by pointer motion. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should consider this event + * as handled. If an event was not handled, the detector + * will continue to accumulate movement until an event is + * handled. This can be useful if an application, for example, + * only wants to update scaling factors if the change is + * greater than 0.01. + */ + + public boolean onScale(ScaleGestureDetector detector); + + /** + * Responds to the beginning of a scaling gesture. Reported by + * new pointers going down. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should continue recognizing + * this gesture. For example, if a gesture is beginning + * with a focal point outside of a region where it makes + * sense, onScaleBegin() may return false to ignore the + * rest of the gesture. + */ + public boolean onScaleBegin(ScaleGestureDetector detector); + + /** + * Responds to the end of a scale gesture. Reported by existing + * pointers going up. + *

+ * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()} + * and {@link ScaleGestureDetector#getFocusY()} will return focal point + * of the pointers remaining on the screen. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + */ + public void onScaleEnd(ScaleGestureDetector detector); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of scaling-related events. This implements all methods in + * {@link OnScaleGestureListener} but does nothing. + * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns + * {@code false} so that a subclass can retrieve the accumulated scale + * factor in an overridden onScaleEnd. + * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns + * {@code true}. + */ + public static class SimpleOnScaleGestureListener implements OnScaleGestureListener { + + public boolean onScale(ScaleGestureDetector detector) { + return false; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + // Intentionally empty + } + } + + private final Context mContext; + private final OnScaleGestureListener mListener; + + private float mFocusX; + private float mFocusY; + + private boolean mQuickScaleEnabled; + private boolean mStylusScaleEnabled; + + private float mCurrSpan; + private float mPrevSpan; + private float mInitialSpan; + private float mCurrSpanX; + private float mCurrSpanY; + private float mPrevSpanX; + private float mPrevSpanY; + private long mCurrTime; + private long mPrevTime; + private boolean mInProgress; + private int mSpanSlop; + private int mMinSpan; + + private final EventHandler mHandler; + + private float mAnchoredScaleStartX; + private float mAnchoredScaleStartY; + private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; + + private static final long TOUCH_STABILIZE_TIME = 128; // ms + private static final float SCALE_FACTOR = .5f; + private static final int ANCHORED_SCALE_MODE_NONE = 0; + private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1; + private static final int ANCHORED_SCALE_MODE_STYLUS = 2; + + + /** + * Consistency verifier for debugging purposes. + */ + private GestureDetector mGestureDetector; + + private boolean mEventBeforeOrAboveStartingGestureEvent; + + /** + * Creates a ScaleGestureDetector with the supplied listener. + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + */ + public ScaleGestureDetector(Context context, OnScaleGestureListener listener) { + this(context, listener, null); + } + + /** + * Creates a ScaleGestureDetector with the supplied listener. + * + * @param context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use for running deferred listener events. + * @throws NullPointerException if {@code listener} is null. + */ + public ScaleGestureDetector(Context context, OnScaleGestureListener listener, + EventHandler handler) { + mContext = context; + mListener = listener; + mSpanSlop = ViewConfiguration.getScaledTouchSlop() * 2; + mMinSpan = ViewConfiguration.getMinimumFlingVelocity(); + mHandler = handler; + + setQuickScaleEnabled(true); + + } + + /** + * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener} + * when appropriate. + * + *

Applications should pass a complete and consistent event stream to this method. + * A complete and consistent event stream involves all MotionEvents from the initial + * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.

+ * + * @param event The event to process + * @return true if the event was processed and the detector wants to receive the + * rest of the MotionEvents in this event stream. + */ + public boolean onTouchEvent(TouchEvent event) { + + mCurrTime = event.getOccurredTime(); + + final int action = event.getAction(); + + // Forward the event to check for double tap gesture + if (mQuickScaleEnabled) { + mGestureDetector.onTouchEvent(event); + } + + final int count = event.getPointerCount(); + final boolean isStylusButtonDown = + (TouchEvent.POINT_MOVE) != 0; + + final boolean anchoredScaleCancelled = + mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown; + final boolean streamComplete = action == TouchEvent.PRIMARY_POINT_UP || + action == TouchEvent.CANCEL || anchoredScaleCancelled; + + if (action == TouchEvent.PRIMARY_POINT_DOWN || streamComplete) { + // Reset any scale in progress with the listener. + // If it's an ACTION_DOWN we're beginning a new event stream. + // This means the app probably didn't give us all the events. Shame on it. + if (mInProgress) { + mListener.onScaleEnd(this); + mInProgress = false; + mInitialSpan = 0; + mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; + } else if (inAnchoredScaleMode() && streamComplete) { + mInProgress = false; + mInitialSpan = 0; + mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE; + } + + if (streamComplete) { + return true; + } + } + + if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode() + && !streamComplete && isStylusButtonDown) { + // Start of a button scale gesture + MmiPoint point = event.getPointerPosition(event.getIndex()); + mAnchoredScaleStartX = point.getX(); + mAnchoredScaleStartY = point.getY(); + mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS; + mInitialSpan = 0; + } + + final boolean configChanged = action == TouchEvent.PRIMARY_POINT_DOWN || + action == TouchEvent.PRIMARY_POINT_UP || + action == TouchEvent.PRIMARY_POINT_DOWN || anchoredScaleCancelled; + + final boolean pointerUp = action == TouchEvent.PRIMARY_POINT_UP; + final int skipIndex = pointerUp ? event.getIndex() : -1; + + // Determine focal point + float sumX = 0, sumY = 0; + final int div = pointerUp ? count - 1 : count; + final float focusX; + final float focusY; + if (inAnchoredScaleMode()) { + // In anchored scale mode, the focal pt is always where the double tap + // or button down gesture started + focusX = mAnchoredScaleStartX; + focusY = mAnchoredScaleStartY; + MmiPoint point = event.getPointerPosition(event.getIndex()); + if (point.getY() < focusY) { + mEventBeforeOrAboveStartingGestureEvent = true; + } else { + mEventBeforeOrAboveStartingGestureEvent = false; + } + } else { + for (int i = 0; i < count; i++) { + if (skipIndex == i) continue; + MmiPoint point = event.getPointerPosition(i); + sumX += point.getX(); + sumY += point.getY(); + } + + focusX = sumX / div; + focusY = sumY / div; + } + + // Determine average deviation from focal point + float devSumX = 0, devSumY = 0; + for (int i = 0; i < count; i++) { + if (skipIndex == i) { + continue; + } + + // Convert the resulting diameter into a radius. + MmiPoint point = event.getPointerPosition(i); + devSumX += Math.abs(point.getX() - focusX); + devSumY += Math.abs(point.getY() - focusY); + } + final float devX = devSumX / div; + final float devY = devSumY / div; + + + final float spanX = devX * 2; + final float spanY = devY * 2; + final float span; + if (inAnchoredScaleMode()) { + span = spanY; + } else { + span = (float) Math.hypot(spanX, spanY); + } + + // Dispatch begin/end events as needed. + // If the configuration changes, notify the app to reset its current state by beginning + // a fresh scale event stream. + final boolean wasInProgress = mInProgress; + mFocusX = focusX; + mFocusY = focusY; + if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) { + mListener.onScaleEnd(this); + mInProgress = false; + mInitialSpan = span; + } + if (configChanged) { + mPrevSpanX = mCurrSpanX = spanX; + mPrevSpanY = mCurrSpanY = spanY; + mInitialSpan = mPrevSpan = mCurrSpan = span; + } + + final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan; + if (!mInProgress && span >= minSpan && + (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) { + mPrevSpanX = mCurrSpanX = spanX; + mPrevSpanY = mCurrSpanY = spanY; + mPrevSpan = mCurrSpan = span; + mPrevTime = mCurrTime; + mInProgress = mListener.onScaleBegin(this); + } + + // Handle motion; focal point and span/scale factor are changing. + if (action == TouchEvent.POINT_MOVE) { + mCurrSpanX = spanX; + mCurrSpanY = spanY; + mCurrSpan = span; + + boolean updatePrev = true; + + if (mInProgress) { + updatePrev = mListener.onScale(this); + } + + if (updatePrev) { + mPrevSpanX = mCurrSpanX; + mPrevSpanY = mCurrSpanY; + mPrevSpan = mCurrSpan; + mPrevTime = mCurrTime; + } + } + + return true; + } + + private boolean inAnchoredScaleMode() { + return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE; + } + + /** + * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks + * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default + * if the app targets API 19 and newer. + * + * @param scales true to enable quick scaling, false to disable + */ + public void setQuickScaleEnabled(boolean scales) { + mQuickScaleEnabled = scales; + if (mQuickScaleEnabled && mGestureDetector == null) { + GestureDetector.SimpleOnGestureListener gestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(TouchEvent e) { + MmiPoint point = e.getPointerPosition(e.getIndex()); + mAnchoredScaleStartX = point.getX(); + mAnchoredScaleStartY = point.getY(); + mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP; + return true; + } + }; + mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler); + } + } + + /** + * Return whether the quick scale gesture, in which the user performs a double tap followed by a + * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}. + */ + public boolean isQuickScaleEnabled() { + return mQuickScaleEnabled; + } + + /** + * Sets whether the associates {@link OnScaleGestureListener} should receive + * onScale callbacks when the user uses a stylus and presses the button. + * Note that this is enabled by default if the app targets API 23 and newer. + * + * @param scales true to enable stylus scaling, false to disable. + */ + public void setStylusScaleEnabled(boolean scales) { + mStylusScaleEnabled = scales; + } + + /** + * Return whether the stylus scale gesture, in which the user uses a stylus and presses the + * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)} + */ + public boolean isStylusScaleEnabled() { + return mStylusScaleEnabled; + } + + /** + * Returns {@code true} if a scale gesture is in progress. + */ + public boolean isInProgress() { + return mInProgress; + } + + /** + * Get the X coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + *

+ * If {@link #isInProgress()} would return false, the result of this + * function is undefined. + * + * @return X coordinate of the focal point in pixels. + */ + public float getFocusX() { + return mFocusX; + } + + /** + * Get the Y coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is between + * each of the pointers forming the gesture. + *

+ * If {@link #isInProgress()} would return false, the result of this + * function is undefined. + * + * @return Y coordinate of the focal point in pixels. + */ + public float getFocusY() { + return mFocusY; + } + + /** + * Return the average distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpan() { + return mCurrSpan; + } + + /** + * Return the average X distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpanX() { + return mCurrSpanX; + } + + /** + * Return the average Y distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpanY() { + return mCurrSpanY; + } + + /** + * Return the previous average distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpan() { + return mPrevSpan; + } + + /** + * Return the previous average X distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpanX() { + return mPrevSpanX; + } + + /** + * Return the previous average Y distance between each of the pointers forming the + * gesture in progress through the focal point. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpanY() { + return mPrevSpanY; + } + + /** + * Return the scaling factor from the previous scale event to the current + * event. This value is defined as + * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}). + * + * @return The current scaling factor. + */ + public float getScaleFactor() { + if (inAnchoredScaleMode()) { + // Drag is moving up; the further away from the gesture + // start, the smaller the span should be, the closer, + // the larger the span, and therefore the larger the scale + final boolean scaleUp = + (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) || + (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan)); + final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR); + return mPrevSpan <= mSpanSlop ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff); + } + return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; + } + + /** + * Return the time difference in milliseconds between the previous + * accepted scaling event and the current scaling event. + * + * @return Time difference since the last scaling event in milliseconds. + */ + public long getTimeDelta() { + return mCurrTime - mPrevTime; + } + + /** + * Return the event time of the current event being processed. + * + * @return Current event time in milliseconds. + */ + public long getEventTime() { + return mCurrTime; + } +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/VelocityTracker.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/VelocityTracker.java new file mode 100644 index 0000000000000000000000000000000000000000..78b0474df34a90dbae151b28591b8e6920057ea3 --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/VelocityTracker.java @@ -0,0 +1,215 @@ +package com.wordplat.ikvstockchart.detector; + +import ohos.agp.components.VelocityDetector; +import ohos.multimodalinput.event.TouchEvent; +import ohos.utils.PlainArray; + +/** + * VelocityTracker + * + * @since 2021-05-09 + */ +public class VelocityTracker { + PlainArray mMap = new PlainArray<>(); + private int mActivePointerId = 0; + private boolean needScale = false; + private float mMaxVelocity = Float.MAX_VALUE; + + /*** + * addMovement + * @param velocityDetector + * @param touchEvent + */ + private void addMovement(VelocityDetector velocityDetector, TouchEvent touchEvent) { + velocityDetector.addEvent(touchEvent); + } + + private float getXVelocity(VelocityDetector velocityDetector) { + return velocityDetector.getHorizontalVelocity(); + } + + private float getYVelocity(VelocityDetector velocityDetector) { + return velocityDetector.getVerticalVelocity(); + } + + /** + * VelocityTracker + */ + public VelocityTracker() { + mMap.put(0, obtainVelocityDetector()); + } + + /** + * addMovement + * + * @param ev + */ + public void addMovement(TouchEvent ev) { + int index = ev.getIndex(); + int id = ev.getPointerId(index); + VelocityDetector detector = mMap.get(id, null); + if (detector == null) { + detector = obtainVelocityDetector(); + mMap.put(id, detector); + } + addMovement(detector, ev); + switch (ev.getAction()) { + case TouchEvent.PRIMARY_POINT_DOWN: + mActivePointerId = id; + break; + case TouchEvent.OTHER_POINT_UP: + if (id == mActivePointerId) { + mActivePointerId = (id == 0) ? 1 : 0; + } + break; + default: + break; + } + } + + /** + * calculateCurrentVelocity + * + * @param units + * @param maxVxVelocity + * @param maxVyVelocity + */ + public void calculateCurrentVelocity(int units, float maxVxVelocity, float maxVyVelocity) { + needScale = false; + mMaxVelocity = Float.MAX_VALUE; + for (int i = 0; i < mMap.size(); i++) { + VelocityDetector vd = mMap.valueAt(i); + if (vd != null) { + vd.calculateCurrentVelocity(units, maxVxVelocity, maxVyVelocity); + } + } + } + + /** + * calculateCurrentVelocity + * + * @param units + * @param maxVelocity + */ + public void calculateCurrentVelocity(int units, float maxVelocity) { + needScale = true; + mMaxVelocity = maxVelocity; + for (int i = 0; i < mMap.size(); i++) { + VelocityDetector vd = mMap.valueAt(i); + if (vd != null) { + vd.calculateCurrentVelocity(units); + } + } + } + + /** + * calculateCurrentVelocity + * + * @param units + */ + public void calculateCurrentVelocity(int units) { + needScale = false; + mMaxVelocity = Float.MAX_VALUE; + for (int i = 0; i < mMap.size(); i++) { + VelocityDetector vd = mMap.valueAt(i); + if (vd != null) { + vd.calculateCurrentVelocity(units); + } + } + } + + public float getXVelocity() { + return getXVelocity(mActivePointerId); + } + + public float getYVelocity() { + return getYVelocity(mActivePointerId); + } + + /** + * getXVelocity + * + * @param id + * @return + */ + public float getXVelocity(int id) { + VelocityDetector vd = mMap.get(id, null); + if (vd == null) { + return 0; + } + return getXVelocityWithScale(vd); + } + + /** + * getYVelocity + * + * @param id + * @return + */ + public float getYVelocity(int id) { + VelocityDetector vd = mMap.get(id, null); + if (vd == null) { + return 0; + } + return getYVelocityWithScale(vd); + } + + /** + * clear + */ + public void clear() { + needScale = false; + mMaxVelocity = Float.MAX_VALUE; + for (int i = 0; i < mMap.size(); i++) { + VelocityDetector vd = mMap.valueAt(i); + if (vd != null) { + velocityclear(vd); + } + } + } + + /** + * recycle + */ + public void recycle() { + clear(); + mMap.clear(); + } + + private VelocityDetector obtainVelocityDetector() { + return VelocityDetector.obtainInstance(); + } + + + private void velocityclear(VelocityDetector velocityDetector) { + velocityDetector.clear(); + } + + private float getXVelocityWithScale(VelocityDetector velocityDetector) { + if (!needScale) { + return getXVelocity(velocityDetector); + } else { + float xVelocity = getXVelocity(velocityDetector); + float yVelocity = getYVelocity(velocityDetector); + if (xVelocity * xVelocity + yVelocity * yVelocity > mMaxVelocity * mMaxVelocity) { + xVelocity /= Math.sqrt((xVelocity * xVelocity + yVelocity * yVelocity) / (mMaxVelocity * mMaxVelocity)); + } + return xVelocity; + } + } + + private float getYVelocityWithScale(VelocityDetector velocityDetector) { + if (!needScale) { + return getYVelocity(velocityDetector); + } else { + float xVelocity = getXVelocity(velocityDetector); + float yVelocity = getYVelocity(velocityDetector); + if (xVelocity * xVelocity + yVelocity * yVelocity > mMaxVelocity * mMaxVelocity) { + yVelocity /= Math.sqrt((xVelocity * xVelocity + yVelocity * yVelocity) / (mMaxVelocity * mMaxVelocity)); + } + return yVelocity; + } + } + + +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ViewConfiguration.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ViewConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..ba65ff501718399435c854acd9bdfd815a918f16 --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/detector/ViewConfiguration.java @@ -0,0 +1,64 @@ +package com.wordplat.ikvstockchart.detector; + +/** + * ViewConfiguration + * + * @since 2021-05-09 + */ +public class ViewConfiguration { + + + private static final int DEFAULT_LONG_PRESS_TIMEOUT = 500; + + private static final int TAP_TIMEOUT = 100; + + private static final int DOUBLE_TAP_TIMEOUT = 300; + + private static final int DOUBLE_TAP_MIN_TIME = 40; + + private static final int TOUCH_SLOP = 100; + + private static final int DOUBLE_TAP_SLOP = 100; + + private static final int MINIMUM_FLING_VELOCITY = 50; + + private static final int MAXIMUM_FLING_VELOCITY = 8000; + + private ViewConfiguration() { + } + + public static int getLongPressTimeout() { + return DEFAULT_LONG_PRESS_TIMEOUT; + } + + public static int getTapTimeout() { + return TAP_TIMEOUT; + } + + public static int getDoubleTapTimeout() { + return DOUBLE_TAP_TIMEOUT; + } + + public static int getDoubleTapMinTime() { + return DOUBLE_TAP_MIN_TIME; + } + + public static int getTouchSlop() { + return TOUCH_SLOP; + } + + public static int getDoubleTapSlop() { + return DOUBLE_TAP_SLOP; + } + + public static int getMinimumFlingVelocity() { + return MINIMUM_FLING_VELOCITY; + } + + public static int getMaximumFlingVelocity() { + return MAXIMUM_FLING_VELOCITY; + } + public static int getScaledTouchSlop() { + return TOUCH_SLOP; + } +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/BOLLDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/BOLLDrawing.java index fc86100725305240cc9abfd2b77b33b49dd7fd2d..ee8e89526ace0f9f4f0a57ce67f6bed7edf03b17 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/BOLLDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/BOLLDrawing.java @@ -18,30 +18,30 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; - import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.Rect; +import ohos.agp.utils.RectFloat; /** *

BOLLDrawing

*

Date: 2017/3/15

* * @author afon + * @since 2021-05-09 */ - -public class BOLLDrawing implements IDrawing{ - +public class BOLLDrawing implements IDrawing { private Paint axisPaint; // X 轴和 Y 轴的画笔 private Paint r1Paint; private Paint r2Paint; private Paint r3Paint; - private final RectF indexRect = new RectF(); + private final RectFloat indexRect = new RectFloat(); private AbstractRender render; private float[] xPointBuffer = new float[4]; @@ -52,41 +52,48 @@ public class BOLLDrawing implements IDrawing{ private float[] gridBuffer = new float[2]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); if (r1Paint == null) { - r1Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r1Paint.setStyle(Paint.Style.STROKE); + r1Paint = new Paint(); + r1Paint.setAntiAlias(true); + r1Paint.setStyle(Paint.Style.STROKE_STYLE); } if (r2Paint == null) { - r2Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r2Paint.setStyle(Paint.Style.STROKE); + r2Paint = new Paint(); + r2Paint.setAntiAlias(true); + r2Paint.setStyle(Paint.Style.STROKE_STYLE); } if (r3Paint == null) { - r3Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r3Paint.setStyle(Paint.Style.STROKE); + r3Paint = new Paint(); + r3Paint.setAntiAlias(true); + r3Paint.setStyle(Paint.Style.STROKE_STYLE); } r1Paint.setStrokeWidth(sizeColor.getBollLineSize()); r2Paint.setStrokeWidth(sizeColor.getBollLineSize()); r3Paint.setStrokeWidth(sizeColor.getBollLineSize()); - r1Paint.setColor(sizeColor.getBollMidLineColor()); - r2Paint.setColor(sizeColor.getBollUpperLineColor()); - r3Paint.setColor(sizeColor.getBollLowerLineColor()); + r1Paint.setColor(new Color(sizeColor.getBollMidLineColor())); + r2Paint.setColor(new Color(sizeColor.getBollUpperLineColor())); + r3Paint.setColor(new Color(sizeColor.getBollLowerLineColor())); - indexRect.set(contentRect); + indexRect.left = contentRect.left; + indexRect.top = contentRect.top; + indexRect.bottom = contentRect.bottom; + indexRect.right = contentRect.right; } @Override @@ -146,7 +153,7 @@ public class BOLLDrawing implements IDrawing{ final int count = (maxIndex - minIndex) * 4; - for (int i = 0 ; i < count ; i = i + 4) { + for (int i = 0; i < count; i = i + 4) { r1Buffer[i + 0] = xPointBuffer[i + 0]; r1Buffer[i + 2] = xPointBuffer[i + 2]; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/CandleDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/CandleDrawing.java index 70ae0d6b838c1891319e7b98b9a86ab2472ea6c7..7fe5d1e5d9a9b3f29ddccf4c0285dc4ec7d2b7d6 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/CandleDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/CandleDrawing.java @@ -18,16 +18,20 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.Log; import com.wordplat.ikvstockchart.compat.ViewUtils; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; import java.text.DecimalFormat; @@ -36,42 +40,67 @@ import java.text.DecimalFormat; *

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public class CandleDrawing implements IDrawing { + /** + * HiLogLabel + */ + static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "CandleDrawing"); + private static final String TAG = "CandleDrawing"; private static final boolean DEBUG = false; - - private Paint candlePaint; // 蜡烛图画笔 - private Paint extremumPaint; // 当前可见区域内的极值画笔 + /** + * 蜡烛图画笔 + */ + private Paint candlePaint; + /** + * 当前可见区域内的极值画笔 + */ + private Paint extremumPaint; private final DecimalFormat decimalFormatter = new DecimalFormat("0.00"); - - private final RectF kLineRect = new RectF(); // K 线图显示区域 + /** + * K 线图显示区域 + */ + private final RectFloat kLineRect = new RectFloat(); private AbstractRender render; - - private float candleSpace = 0.1f; // entry 与 entry 之间的间隙,默认 0.1f (10%) + /** + * entry 与 entry 之间的间隙,默认 0.1f (10%) + */ + private float candleSpace = 0.1f; private float extremumToRight; - private float[] candleLineBuffer = new float[8]; // 计算 2 根线坐标用的 - private float[] candleRectBuffer = new float[4]; // 计算 1 个矩形坐标用的 + /** + * 计算 2 根线坐标用的 + */ + private float[] candleLineBuffer = new float[8]; + /** + * 计算 1 个矩形坐标用的 + */ + private float[] candleRectBuffer = new float[4]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (candlePaint == null) { - candlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - candlePaint.setStyle(Paint.Style.FILL); + candlePaint = new Paint(); + candlePaint.setAntiAlias(true); + candlePaint.setStyle(Paint.Style.FILL_STYLE); candlePaint.setStrokeWidth(sizeColor.getCandleBorderSize()); } if (extremumPaint == null) { - extremumPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + extremumPaint = new Paint(); + extremumPaint.setAntiAlias(true); } - extremumPaint.setTextSize(sizeColor.getCandleExtremumLabelSize()); - extremumPaint.setColor(sizeColor.getCandleExtremumLableColor()); + extremumPaint.setTextSize(StringUtils.floatToInt(sizeColor.getCandleExtremumLabelSize())); + extremumPaint.setColor(new Color(sizeColor.getCandleExtremumLableColor())); - kLineRect.set(contentRect); + kLineRect.left = contentRect.left; + kLineRect.top = contentRect.top; + kLineRect.bottom = contentRect.bottom; + kLineRect.right = contentRect.right; extremumToRight = kLineRect.right - 150; } @@ -89,19 +118,14 @@ public class CandleDrawing implements IDrawing { canvas.save(); canvas.clipRect(kLineRect); -// if (DEBUG) { -// Log.i(TAG, "##d onComputeOver: minIndex = " + minIndex + ", maxIndex = " + maxIndex -// + ", minYIndex = " + entrySet.getMinYIndex() + ", maxYIndex = " + entrySet.getMaxYIndex()); -// } - - for (int i = minIndex; i < maxIndex; i++) { - Entry entry = ViewUtils.setUpCandlePaint(candlePaint, entrySet, i, sizeColor); + for (int iindex = minIndex; iindex < maxIndex; iindex++) { + Entry entry = ViewUtils.setUpCandlePaint(candlePaint, entrySet, iindex, sizeColor); // 绘制 影线 - candleLineBuffer[0] = i + 0.5f; - candleLineBuffer[2] = i + 0.5f; - candleLineBuffer[4] = i + 0.5f; - candleLineBuffer[6] = i + 0.5f; + candleLineBuffer[0] = iindex + 0.5f; + candleLineBuffer[2] = iindex + 0.5f; + candleLineBuffer[4] = iindex + 0.5f; + candleLineBuffer[6] = iindex + 0.5f; if (entry.getOpen() > entry.getClose()) { candleLineBuffer[1] = entry.getHigh(); candleLineBuffer[3] = entry.getOpen(); @@ -116,45 +140,45 @@ public class CandleDrawing implements IDrawing { render.mapPoints(candleLineBuffer); canvas.drawLines(candleLineBuffer, candlePaint); - // 绘制 当前显示区域的"最小"与"最大"两个值 - if (i == entrySet.getMinYIndex()) { + /** + * 绘制 当前显示区域的"最小"与"最大"两个值 + */ + if (iindex == entrySet.getMinYIndex()) { if (candleLineBuffer[6] > extremumToRight) { - extremumPaint.setTextAlign(Paint.Align.RIGHT); + extremumPaint.setTextAlign(TextAlignment.RIGHT); - canvas.drawText(decimalFormatter.format(entry.getLow()) + " →", + canvas.drawText(extremumPaint, decimalFormatter.format(entry.getLow()) + " →", candleLineBuffer[6], - candleLineBuffer[7] + 20, - extremumPaint); + candleLineBuffer[7] + 20); } else { - extremumPaint.setTextAlign(Paint.Align.LEFT); + extremumPaint.setTextAlign(TextAlignment.LEFT); - canvas.drawText("← " + decimalFormatter.format(entry.getLow()), + canvas.drawText(extremumPaint, "← " + decimalFormatter.format(entry.getLow()), candleLineBuffer[6], - candleLineBuffer[7] + 20, - extremumPaint); + candleLineBuffer[7] + 20); } } - if (i == entrySet.getMaxYIndex()) { + if (iindex == entrySet.getMaxYIndex()) { if (candleLineBuffer[0] > extremumToRight) { - extremumPaint.setTextAlign(Paint.Align.RIGHT); + extremumPaint.setTextAlign(TextAlignment.RIGHT); - canvas.drawText(decimalFormatter.format(entry.getHigh()) + " →", + canvas.drawText(extremumPaint, decimalFormatter.format(entry.getHigh()) + " →", candleLineBuffer[0], - candleLineBuffer[1] - 5, - extremumPaint); + candleLineBuffer[1] - 5); } else { - extremumPaint.setTextAlign(Paint.Align.LEFT); + extremumPaint.setTextAlign(TextAlignment.LEFT); - canvas.drawText("← " + decimalFormatter.format(entry.getHigh()), + canvas.drawText(extremumPaint, "← " + decimalFormatter.format(entry.getHigh()), candleLineBuffer[0], - candleLineBuffer[1] - 5, - extremumPaint); + candleLineBuffer[1] - 5); } } - // 绘制 蜡烛图的矩形 - candleRectBuffer[0] = i + candleSpace; - candleRectBuffer[2] = i + 1 - candleSpace; + /** + * 绘制 蜡烛图的矩形 + */ + candleRectBuffer[0] = iindex + candleSpace; + candleRectBuffer[2] = iindex + 1 - candleSpace; if (entry.getOpen() > entry.getClose()) { candleRectBuffer[1] = entry.getOpen(); @@ -166,25 +190,28 @@ public class CandleDrawing implements IDrawing { render.mapPoints(candleRectBuffer); if (DEBUG) { - if (i == minIndex || i == maxIndex - 1) { - Log.i(TAG, "##d onComputeOver: i = " + i + ", candleRectBuffer = " + candleRectBuffer[0] + " - " + candleRectBuffer[2]); + if (iindex == minIndex || iindex == maxIndex - 1) { + HiLog.info(LABEL, "##d onComputeOver: iindex = " + iindex + ", candleRectBuffer = " + candleRectBuffer[0] + " - " + candleRectBuffer[2]); } } - - if (Math.abs(candleRectBuffer[1] - candleRectBuffer[3]) < 1.f) { // 涨停、跌停、或不涨不跌的一字板 + /** + * 涨停、跌停、或不涨不跌的一字板 + */ + if (Math.abs(candleRectBuffer[1] - candleRectBuffer[3]) < 1.f) { canvas.drawRect(candleRectBuffer[0], candleRectBuffer[1], candleRectBuffer[2], candleRectBuffer[3] + 2, candlePaint); } else { canvas.drawRect(candleRectBuffer[0], candleRectBuffer[1], candleRectBuffer[2], candleRectBuffer[3], candlePaint); } - // 计算高亮坐标 + /** + * 计算高亮坐标 + */ if (render.isHighlight()) { final float[] highlightPoint = render.getHighlightPoint(); if (candleRectBuffer[0] <= highlightPoint[0] && highlightPoint[0] <= candleRectBuffer[2]) { highlightPoint[0] = candleLineBuffer[0]; -// highlightPoint[1] = (candleRectBuffer[1] + candleRectBuffer[3]) / 2; - entrySet.setHighlightIndex(i); + entrySet.setHighlightIndex(iindex); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/EmptyDataDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/EmptyDataDrawing.java index 20f23ae5dd33c27a8e39f859e524bdf797984c61..f109c56b1c9a3c630d73290e34d41447b5f4fbf3 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/EmptyDataDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/EmptyDataDrawing.java @@ -18,39 +18,47 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; /** *

EmptyDataDrawing

*

Date: 2017/3/21

* * @author afon + * @since 2021-05-09 */ - public class EmptyDataDrawing implements IDrawing { - private Paint textPaint; - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0, 0, 0, 0, 0); private AbstractRender render; private SizeColor sizeColor; - private final RectF contentRect = new RectF(); + private final RectFloat contentRect = new RectFloat(); @Override - public void onInit(RectF contentRect, AbstractRender render) { - this.render = render; - this.contentRect.set(contentRect); - this.sizeColor = render.getSizeColor(); + public void onInit(RectFloat rectFloat, AbstractRender abstractRender) { + this.render = abstractRender; + + this.contentRect.left = rectFloat.left; + this.contentRect.top = rectFloat.top; + this.contentRect.bottom = rectFloat.bottom; + this.contentRect.right = rectFloat.right; + + this.sizeColor = abstractRender.getSizeColor(); if (textPaint == null) { - textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint = new Paint(); + textPaint.setAntiAlias(true); } - textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setTextAlign(TextAlignment.CENTER); } @Override @@ -68,21 +76,20 @@ public class EmptyDataDrawing implements IDrawing { if (render.getEntrySet().getEntryList().size() == 0) { final String drawText; if (render.getEntrySet().isLoadingStatus()) { - textPaint.setTextSize(sizeColor.getLoadingTextSize()); - textPaint.setColor(sizeColor.getLoadingTextColor()); + textPaint.setTextSize(StringUtils.floatToInt(sizeColor.getLoadingTextSize())); + textPaint.setColor(new Color(sizeColor.getLoadingTextColor())); drawText = sizeColor.getLoadingText(); } else { - textPaint.setTextSize(sizeColor.getErrorTextSize()); - textPaint.setColor(sizeColor.getErrorTextColor()); + + textPaint.setTextSize(StringUtils.floatToInt(sizeColor.getErrorTextSize())); + textPaint.setColor(new Color(sizeColor.getErrorTextColor())); drawText = sizeColor.getErrorText(); } + textPaint.getFontMetrics(); - textPaint.getFontMetrics(fontMetrics); - - canvas.drawText(drawText, - contentRect.width() / 2, - (contentRect.top + contentRect.bottom - fontMetrics.top - fontMetrics.bottom) / 2, - textPaint); + canvas.drawText(textPaint, drawText, + contentRect.getWidth() / 2, + (contentRect.top + contentRect.bottom - fontMetrics.top - fontMetrics.bottom) / 2); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/HighlightDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/HighlightDrawing.java index 9b514b5938bc3ab9d93c435a13e5b3e1f8ccbcf6..08a7b4494d225117a1ffaee33458ec56bf846755 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/HighlightDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/HighlightDrawing.java @@ -18,14 +18,14 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.text.TextPaint; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.marker.IMarkerView; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; import java.util.ArrayList; import java.util.List; @@ -35,44 +35,63 @@ import java.util.List; *

Date: 2017/3/23

* * @author afon + * @since 2021-05-09 */ - public class HighlightDrawing implements IDrawing { - - protected Paint highlightPaint; // 高亮线条画笔 - - protected final RectF contentRect = new RectF(); // 绘图区域 + /** + * 高亮线条画笔 + */ + protected Paint highlightPaint; + /** + * 绘图区域 + */ + protected final RectFloat contentRect = new RectFloat(); protected AbstractRender render; private List markerViewList = new ArrayList<>(); + /** + * HighlightDrawing + */ public HighlightDrawing() { } + /** + * HighlightDrawing + * @param markerViews + */ public HighlightDrawing(IMarkerView... markerViews) { for (IMarkerView markerView : markerViews) { addMarkerView(markerView); } } + /** + * addMarkerView + * @param markerView + */ public void addMarkerView(IMarkerView markerView) { markerViewList.add(markerView); } @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (highlightPaint == null) { - highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - highlightPaint.setStyle(Paint.Style.STROKE); + highlightPaint = new Paint(); + highlightPaint.setAntiAlias(true); + highlightPaint.setStyle(Paint.Style.STROKE_STYLE); } highlightPaint.setStrokeWidth(sizeColor.getHighlightSize()); - highlightPaint.setColor(sizeColor.getHighlightColor()); + highlightPaint.setColor(new Color(sizeColor.getHighlightColor())); - this.contentRect.set(contentRect); + this.contentRect.left = contentRect.left; + this.contentRect.top = contentRect.top; + this.contentRect.bottom = contentRect.bottom; + this.contentRect.right = contentRect.right; if (markerViewList.size() > 0) { for (IMarkerView markerView : markerViewList) { @@ -93,7 +112,9 @@ public class HighlightDrawing implements IDrawing { @Override public void onDrawOver(Canvas canvas) { - // 绘制高亮 + /** + * 绘制高亮 + */ if (render.isHighlight()) { final float[] highlightPoint = render.getHighlightPoint(); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/IDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/IDrawing.java index 7c7470fe3197b91e06aba03fed00c7186542b5b3..a56947f98dc9c4a5461a53724b21a7652098b13f 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/IDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/IDrawing.java @@ -18,18 +18,18 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.RectF; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.utils.RectFloat; /** *

IDrawing

*

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public interface IDrawing { /** @@ -38,7 +38,7 @@ public interface IDrawing { * @param contentRect 视图区域 * @param render render */ - void onInit(RectF contentRect, AbstractRender render); + void onInit(RectFloat contentRect, AbstractRender render); /** * 计算预绘制的坐标 diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KDJDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KDJDrawing.java index 12a782216723963cac8ac12bfca6f47f92e1cb9a..4b12a94aaf7edf85fdd83b9f96cfd11c5db3c3bb 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KDJDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KDJDrawing.java @@ -18,30 +18,33 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; - import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; + /** *

KDJDrawing

*

Date: 2017/3/15

* * @author afon + * @since 2021-05-09 */ - public class KDJDrawing implements IDrawing { - - private Paint axisPaint; // X 轴和 Y 轴的画笔 + /** + * X 轴和 Y 轴的画笔 + */ + private Paint axisPaint; private Paint kPaint; private Paint dPaint; private Paint jPaint; - private final RectF indexRect = new RectF(); + private final RectFloat indexRect = new RectFloat(); private AbstractRender render; private float[] xPointBuffer = new float[4]; @@ -52,41 +55,48 @@ public class KDJDrawing implements IDrawing { private float[] gridBuffer = new float[2]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); if (kPaint == null) { - kPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - kPaint.setStyle(Paint.Style.STROKE); + kPaint = new Paint(); + kPaint.setAntiAlias(true); + kPaint.setStyle(Paint.Style.STROKE_STYLE); } if (dPaint == null) { - dPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - dPaint.setStyle(Paint.Style.STROKE); + dPaint = new Paint(); + dPaint.setAntiAlias(true); + dPaint.setStyle(Paint.Style.STROKE_STYLE); } if (jPaint == null) { - jPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - jPaint.setStyle(Paint.Style.STROKE); + jPaint = new Paint(); + jPaint.setAntiAlias(true); + jPaint.setStyle(Paint.Style.STROKE_STYLE); } kPaint.setStrokeWidth(sizeColor.getKdjLineSize()); dPaint.setStrokeWidth(sizeColor.getKdjLineSize()); jPaint.setStrokeWidth(sizeColor.getKdjLineSize()); - kPaint.setColor(sizeColor.getKdjKLineColor()); - dPaint.setColor(sizeColor.getKdjDLineColor()); - jPaint.setColor(sizeColor.getKdjJLineColor()); + kPaint.setColor(new Color(sizeColor.getKdjKLineColor())); + dPaint.setColor(new Color(sizeColor.getKdjDLineColor())); + jPaint.setColor(new Color(sizeColor.getKdjJLineColor())); - indexRect.set(contentRect); + indexRect.left = contentRect.left; + indexRect.top = contentRect.top; + indexRect.bottom = contentRect.bottom; + indexRect.right = contentRect.right; } @Override @@ -146,7 +156,7 @@ public class KDJDrawing implements IDrawing { final int count = (maxIndex - minIndex) * 4; - for (int i = 0 ; i < count ; i = i + 4) { + for (int i = 0; i < count; i = i + 4) { kBuffer[i + 0] = xPointBuffer[i + 0]; kBuffer[i + 2] = xPointBuffer[i + 2]; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineGridAxisDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineGridAxisDrawing.java index ec83c4eedfa65195c10f5497afb25b1b4bc1c23b..0a447c05ed43b89efeff995228d51d8c14f90122 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineGridAxisDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineGridAxisDrawing.java @@ -18,15 +18,18 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.align.YLabelAlign; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; import com.wordplat.ikvstockchart.render.KLineRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; import java.text.DecimalFormat; @@ -35,67 +38,92 @@ import java.text.DecimalFormat; *

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public class KLineGridAxisDrawing implements IDrawing { - - private Paint xLabelPaint; // X 轴标签的画笔 - private Paint yLabelPaint; // Y 轴标签的画笔 - private Paint axisPaint; // X 轴和 Y 轴的画笔 - private Paint gridPaint; // k线图网格线画笔 - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); // 用于 labelPaint 计算文字位置 + /** + * X 轴标签的画笔 + */ + private Paint xLabelPaint; + /** + * Y 轴标签的画笔 + */ + private Paint yLabelPaint; + /** + * X 轴和 Y 轴的画笔 + */ + private Paint axisPaint; + /** + * k线图网格线画笔 + */ + private Paint gridPaint; + /** + * 用于 labelPaint 计算文字位置 + */ + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0, 0, 0, 0, 0); private final DecimalFormat decimalFormatter = new DecimalFormat("0.00"); - - private final RectF kLineRect = new RectF(); // K 线图显示区域 + /** + * K 线图显示区域 + */ + private final RectFloat kLineRect = new RectFloat(); private KLineRender render; private final float[] pointCache = new float[2]; private float lineHeight; - - private YLabelAlign yLabelAlign; // Y 轴标签对齐方向 + /** + * Y 轴标签对齐方向 + */ + private YLabelAlign yLabelAlign; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = (KLineRender) render; final SizeColor sizeColor = render.getSizeColor(); if (xLabelPaint == null) { - xLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + xLabelPaint = new Paint(); + xLabelPaint.setAntiAlias(true); } - xLabelPaint.setTextSize(sizeColor.getXLabelSize()); - xLabelPaint.setColor(sizeColor.getXLabelColor()); - xLabelPaint.setTextAlign(Paint.Align.CENTER); - xLabelPaint.getFontMetrics(fontMetrics); + xLabelPaint.setTextSize(StringUtils.floatToInt(sizeColor.getXLabelSize())); + xLabelPaint.setColor(new Color(sizeColor.getXLabelColor())); + xLabelPaint.setTextAlign(TextAlignment.CENTER); + xLabelPaint.getFontMetrics(); if (yLabelPaint == null) { - yLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + yLabelPaint = new Paint(); + yLabelPaint.setAntiAlias(true); } - yLabelPaint.setTextSize(sizeColor.getYLabelSize()); - yLabelPaint.setColor(sizeColor.getYLabelColor()); + yLabelPaint.setTextSize(StringUtils.floatToInt(sizeColor.getYLabelSize())); + yLabelPaint.setColor(new Color(sizeColor.getYLabelColor())); yLabelAlign = sizeColor.getYLabelAlign(); if (yLabelAlign == YLabelAlign.RIGHT) { - yLabelPaint.setTextAlign(Paint.Align.RIGHT); + yLabelPaint.setTextAlign(TextAlignment.RIGHT); } if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } if (gridPaint == null) { - gridPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - gridPaint.setStyle(Paint.Style.STROKE); + gridPaint = new Paint(); + gridPaint.setAntiAlias(true); + gridPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); gridPaint.setStrokeWidth(sizeColor.getGridSize()); - gridPaint.setColor(sizeColor.getGridColor()); + gridPaint.setColor(new Color(sizeColor.getGridColor())); - kLineRect.set(contentRect); + kLineRect.left = contentRect.left; + kLineRect.top = contentRect.top; + kLineRect.bottom = contentRect.bottom; + kLineRect.right = contentRect.right; - lineHeight = kLineRect.height() / 4; + lineHeight = kLineRect.getHeight() / 4; } @Override @@ -107,11 +135,15 @@ public class KLineGridAxisDrawing implements IDrawing { public void onComputeOver(Canvas canvas, int minIndex, int maxIndex, float minY, float maxY) { final EntrySet entrySet = render.getEntrySet(); final SizeColor sizeColor = render.getSizeColor(); - // 绘制 最外层大框框 + /** + * 绘制 最外层大框框 + */ canvas.drawRect(kLineRect, axisPaint); - // 绘制 三条横向网格线 - for (int i = 0 ; i < 3 ; i++) { + /** + * 绘制 三条横向网格线 + */ + for (int i = 0; i < 3; i++) { float lineTop = kLineRect.top + (i + 1) * lineHeight; canvas.drawLine(kLineRect.left, lineTop, kLineRect.right, lineTop, gridPaint); } @@ -119,11 +151,15 @@ public class KLineGridAxisDrawing implements IDrawing { canvas.save(); canvas.clipRect(kLineRect.left, kLineRect.top, kLineRect.right, kLineRect.bottom + sizeColor.getXLabelViewHeight()); - // 每隔特定个 entry,绘制一条竖向网格线和 X 轴 label + /** + * 每隔特定个 entry,绘制一条竖向网格线和 X 轴 label + */ final int count = render.getZoomTimes() < 0 ? Math.abs(7 * render.getZoomTimes()) + 2 : 7; final int lastIndex = entrySet.getEntryList().size() - 1; for (int i = minIndex; i < maxIndex; i++) { - // 跳过首个 entry 和最后一个 entry,因为画出来不好看 + /** + * 跳过首个 entry 和最后一个 entry,因为画出来不好看 + */ if (i == 0 || i == lastIndex) { continue; } @@ -131,13 +167,14 @@ public class KLineGridAxisDrawing implements IDrawing { pointCache[0] = i + 0.5f; render.mapPoints(pointCache); - canvas.drawText( + canvas.drawText(xLabelPaint, entrySet.getEntryList().get(i).getXLabel(), pointCache[0], - kLineRect.bottom + render.getSizeColor().getXLabelSize(), - xLabelPaint); + kLineRect.bottom + render.getSizeColor().getXLabelSize()); - // 跳过超出显示区域的线 + /** + * 跳过超出显示区域的线 + */ if (pointCache[0] < kLineRect.left || pointCache[0] > kLineRect.right) { continue; } @@ -150,8 +187,10 @@ public class KLineGridAxisDrawing implements IDrawing { @Override public void onDrawOver(Canvas canvas) { - // 绘制 Y 轴 label - for (int i = 0 ; i < 5 ; i++) { + /** + * 绘制 Y 轴 label + */ + for (int i = 0; i < 5; i++) { float lineTop = kLineRect.top + i * lineHeight; pointCache[1] = lineTop; render.invertMapPoints(pointCache); @@ -167,7 +206,7 @@ public class KLineGridAxisDrawing implements IDrawing { float labelX = yLabelAlign == YLabelAlign.LEFT ? kLineRect.left + 5 : kLineRect.right - 5; - canvas.drawText(value, labelX, pointCache[0], yLabelPaint); + canvas.drawText(yLabelPaint, value, labelX, pointCache[0]); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeDrawing.java index 4176c55c779c0792c39c4b82b02604ebeefd7603..8054d9e3eec3532f694f68328fa04f4946aa861a 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeDrawing.java @@ -1,78 +1,102 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.Log; import com.wordplat.ikvstockchart.compat.ViewUtils; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; /** *

KLineVolumeDrawing K线成交量的绘制

*

Date: 2017/6/28

* * @author afon + * @since 2021-05-09 */ - public class KLineVolumeDrawing implements IDrawing { private static final String TAG = "KLineVolumeDrawing"; - - private Paint axisPaint; // X 轴和 Y 轴的画笔 - private Paint ma5Paint; // 成交量的5日平均线画笔 - private Paint ma10Paint; // 成交量的10日平均线画笔 - private Paint candlePaint; // 成交量画笔 - - private final RectF candleRect = new RectF(); // 绘图区域 + /** + * X 轴和 Y 轴的画笔 + */ + private Paint axisPaint; + /** + * 成交量的5日平均线画笔 + */ + private Paint ma5Paint; + /** + * 成交量的10日平均线画笔 + */ + private Paint ma10Paint; + /** + * 成交量画笔 + */ + private Paint candlePaint; + /** + * 绘图区域 + */ + private final RectFloat candleRect = new RectFloat(); private AbstractRender render; - // 计算 MA(5, 10) 线条坐标用的 + /** + * 计算 MA(5, 10) 线条坐标用的 + */ private float[] xPointBuffer = new float[4]; private float[] ma5Buffer = new float[4]; private float[] ma10Buffer = new float[4]; - - private float candleSpace = 0.1f; // entry 与 entry 之间的间隙,默认 0.1f (10%) + /** + * entry 与 entry 之间的间隙,默认 0.1f (10%) + */ + private float candleSpace = 0.1f; private float[] xRectBuffer = new float[4]; private float[] candleBuffer = new float[4]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); if (ma5Paint == null) { - ma5Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - ma5Paint.setStyle(Paint.Style.STROKE); + ma5Paint = new Paint(); + ma5Paint.setAntiAlias(true); + ma5Paint.setStyle(Paint.Style.STROKE_STYLE); } if (ma10Paint == null) { - ma10Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - ma10Paint.setStyle(Paint.Style.STROKE); + ma10Paint = new Paint(); + ma10Paint.setAntiAlias(true); + ma10Paint.setStyle(Paint.Style.STROKE_STYLE); } if (candlePaint == null) { - candlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - candlePaint.setStyle(Paint.Style.FILL); + candlePaint = new Paint(); + candlePaint.setAntiAlias(true); + candlePaint.setStyle(Paint.Style.FILL_STYLE); candlePaint.setStrokeWidth(sizeColor.getCandleBorderSize()); } ma5Paint.setStrokeWidth(sizeColor.getMaLineSize()); ma10Paint.setStrokeWidth(sizeColor.getMaLineSize()); - ma5Paint.setColor(sizeColor.getMa5Color()); - ma10Paint.setColor(sizeColor.getMa10Color()); + ma5Paint.setColor(new Color(sizeColor.getMa5Color())); + ma10Paint.setColor(new Color(sizeColor.getMa10Color())); - candleRect.set(contentRect); + candleRect.left = contentRect.left; + candleRect.top = contentRect.top; + candleRect.bottom = contentRect.bottom; + candleRect.right = contentRect.right; } @Override @@ -117,10 +141,14 @@ public class KLineVolumeDrawing implements IDrawing { canvas.drawRect(candleRect, axisPaint); for (int i = minIndex; i < maxIndex; i++) { - // 设置画笔颜色 + /** + * 设置画笔颜色 + */ Entry entry = ViewUtils.setUpCandlePaint(candlePaint, entrySet, i, sizeColor); - // 计算 成交量的矩形卓坐标 + /** + * 计算 成交量的矩形卓坐标 + */ xRectBuffer[0] = i + candleSpace; xRectBuffer[1] = 0; xRectBuffer[2] = i + 1 - candleSpace; @@ -133,22 +161,26 @@ public class KLineVolumeDrawing implements IDrawing { candleBuffer[3] = minY; render.mapPoints(null, candleBuffer); - - if (Math.abs(candleBuffer[1] - candleBuffer[3]) < 1.f) { // 成交量非常小画一条直线 + /** + * 成交量非常小画一条直线 + */ + if (Math.abs(candleBuffer[1] - candleBuffer[3]) < 1.f) { canvas.drawRect(xRectBuffer[0], candleBuffer[1] - 2, xRectBuffer[2], candleBuffer[1], candlePaint); } else { canvas.drawRect(xRectBuffer[0], candleBuffer[1], xRectBuffer[2], candleBuffer[3] - axisPaint.getStrokeWidth(), candlePaint); } } - // 映射坐标,绘制成交量的5日平均线和10日平均线 + /** + * 映射坐标,绘制成交量的5日平均线和10日平均线 + */ render.mapPoints(xPointBuffer); render.mapPoints(null, ma5Buffer); render.mapPoints(null, ma10Buffer); final int count = (maxIndex - minIndex) * 4; - for (int i = 0 ; i < count ; i = i + 4) { + for (int i = 0; i < count; i = i + 4) { ma5Buffer[i + 0] = xPointBuffer[i + 0]; ma5Buffer[i + 2] = xPointBuffer[i + 2]; @@ -156,7 +188,9 @@ public class KLineVolumeDrawing implements IDrawing { ma10Buffer[i + 2] = xPointBuffer[i + 2]; } - // 使用 drawLines 方法比依次调用 drawLine 方法要快 + /** + * 使用 drawLines 方法比依次调用 drawLine 方法要快 + */ canvas.drawLines(ma5Buffer, 0, count, ma5Paint); canvas.drawLines(ma10Buffer, 0, count, ma10Paint); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeHighlightDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeHighlightDrawing.java index cfd8ff8dfad4eb90fba882a192298d9057bda1ea..78fe5ffacf8d6460b33a3d504a405cbd11daa4bd 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeHighlightDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/KLineVolumeHighlightDrawing.java @@ -1,19 +1,22 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; + +import ohos.agp.render.Canvas; /** *

KLineVolumeHighlightDrawing K线成交量的高亮绘制

*

Date: 2017/6/28

* * @author afon + * @since 2021-05-09 */ - public class KLineVolumeHighlightDrawing extends HighlightDrawing { @Override public void onDrawOver(Canvas canvas) { - // 绘制高亮 成交量的高亮线条不需要垂直移动 + /** + * 绘制高亮 成交量的高亮线条不需要垂直移动 + */ if (render.isHighlight()) { final float[] highlightPoint = render.getHighlightPoint(); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MACDDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MACDDrawing.java index e27ea42df54e628e236857972da5bf0d26763f86..3b37c587a886e23ba497febafa794b90a4733d52 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MACDDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MACDDrawing.java @@ -18,22 +18,24 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; + /** *

MACDDrawing

*

Date: 2017/3/14

* * @author afon + * @since 2021-05-09 */ - public class MACDDrawing implements IDrawing { private static final String TAG = "MACDDrawing"; @@ -42,7 +44,7 @@ public class MACDDrawing implements IDrawing { private Paint difPaint; private Paint macdPaint; - private final RectF indexRect = new RectF(); + private final RectFloat indexRect = new RectFloat(); private AbstractRender render; private float candleSpace = 0.1f; @@ -57,41 +59,48 @@ public class MACDDrawing implements IDrawing { private float[] macdBuffer = new float[4]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); if (deaPaint == null) { - deaPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - deaPaint.setStyle(Paint.Style.STROKE); + deaPaint = new Paint(); + deaPaint.setAntiAlias(true); + deaPaint.setStyle(Paint.Style.STROKE_STYLE); } if (difPaint == null) { - difPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - difPaint.setStyle(Paint.Style.STROKE); + difPaint = new Paint(); + difPaint.setAntiAlias(true); + difPaint.setStyle(Paint.Style.STROKE_STYLE); } if (macdPaint == null) { - macdPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - macdPaint.setStyle(Paint.Style.FILL); + macdPaint = new Paint(); + macdPaint.setAntiAlias(true); + macdPaint.setStyle(Paint.Style.FILL_STYLE); macdPaint.setStrokeWidth(sizeColor.getMacdLineSize()); } - macdPaint.setColor(sizeColor.getIncreasingColor()); + macdPaint.setColor(new Color(sizeColor.getIncreasingColor())); deaPaint.setStrokeWidth(sizeColor.getMacdLineSize()); difPaint.setStrokeWidth(sizeColor.getMacdLineSize()); - deaPaint.setColor(sizeColor.getDeaLineColor()); - difPaint.setColor(sizeColor.getDiffLineColor()); + deaPaint.setColor(new Color(sizeColor.getDeaLineColor())); + difPaint.setColor(new Color(sizeColor.getDiffLineColor())); - indexRect.set(contentRect); + indexRect.left = contentRect.left; + indexRect.top = contentRect.top; + indexRect.bottom = contentRect.bottom; + indexRect.right = contentRect.right; } @Override @@ -167,9 +176,9 @@ public class MACDDrawing implements IDrawing { render.mapPoints(null, macdBuffer); if (macdBuffer[3] <= gridBuffer[1]) { - macdPaint.setColor(sizeColor.getIncreasingColor()); + macdPaint.setColor(new Color(sizeColor.getIncreasingColor())); } else { - macdPaint.setColor(sizeColor.getDecreasingColor()); + macdPaint.setColor(new Color(sizeColor.getDecreasingColor())); } canvas.drawRect(xRectBuffer[0], macdBuffer[1], xRectBuffer[2], macdBuffer[3], macdPaint); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MADrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MADrawing.java index 7d7f6d7dbf2a753163bd8fb6bc85b96fdf3a71f0..8bce6c6637216b815734e78c514597d0c8c2d9d3 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MADrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/MADrawing.java @@ -18,65 +18,77 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; + /** *

MADrawing

*

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public class MADrawing implements IDrawing { private Paint ma5Paint; private Paint ma10Paint; private Paint ma20Paint; - - private final RectF candleRect = new RectF(); // K 线图显示区域 + /** + * K 线图显示区域 + */ + private final RectFloat candleRect = new RectFloat(); private AbstractRender render; - // 计算 MA(5, 10, 20) 线条坐标用的 + /** + * 计算 MA(5, 10, 20) 线条坐标用的 + */ private float[] ma5Buffer = new float[4]; private float[] ma10Buffer = new float[4]; private float[] ma20Buffer = new float[4]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (ma5Paint == null) { - ma5Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - ma5Paint.setStyle(Paint.Style.STROKE); + ma5Paint = new Paint(); + ma5Paint.setAntiAlias(true); + ma5Paint.setStyle(Paint.Style.STROKE_STYLE); } if (ma10Paint == null) { - ma10Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - ma10Paint.setStyle(Paint.Style.STROKE); + ma10Paint = new Paint(); + ma10Paint.setAntiAlias(true); + ma10Paint.setStyle(Paint.Style.STROKE_STYLE); } if (ma20Paint == null) { - ma20Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - ma20Paint.setStyle(Paint.Style.STROKE); + ma20Paint = new Paint(); + ma20Paint.setAntiAlias(true); + ma20Paint.setStyle(Paint.Style.STROKE_STYLE); } ma5Paint.setStrokeWidth(sizeColor.getMaLineSize()); ma10Paint.setStrokeWidth(sizeColor.getMaLineSize()); ma20Paint.setStrokeWidth(sizeColor.getMaLineSize()); - ma5Paint.setColor(sizeColor.getMa5Color()); - ma10Paint.setColor(sizeColor.getMa10Color()); - ma20Paint.setColor(sizeColor.getMa20Color()); + ma5Paint.setColor(new Color(sizeColor.getMa5Color())); + ma10Paint.setColor(new Color(sizeColor.getMa10Color())); + ma20Paint.setColor(new Color(sizeColor.getMa20Color())); - candleRect.set(contentRect); + candleRect.left = contentRect.left; + candleRect.top = contentRect.top; + candleRect.bottom = contentRect.bottom; + candleRect.right = contentRect.right; } @Override @@ -121,7 +133,9 @@ public class MADrawing implements IDrawing { final int count = (maxIndex - minIndex) * 4; - // 使用 drawLines 方法比依次调用 drawLine 方法要快 + /** + * 使用 drawLines 方法比依次调用 drawLine 方法要快 + */ canvas.drawLines(ma5Buffer, 0, count, ma5Paint); canvas.drawLines(ma10Buffer, 0, count, ma10Paint); canvas.drawLines(ma20Buffer, 0, count, ma20Paint); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/RSIDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/RSIDrawing.java index 0d84fe298df7357c3914e6a09fffcc9ac9a3ba70..66ddae07537154b97c10b3942d5596f378717074 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/RSIDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/RSIDrawing.java @@ -18,30 +18,34 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; + /** *

RSIDrawing

*

Date: 2017/3/15

* * @author afon + * @since 2021-05-09 */ - public class RSIDrawing implements IDrawing { - - private Paint axisPaint; // X 轴和 Y 轴的画笔 + /** + * X 轴和 Y 轴的画笔 + */ + private Paint axisPaint; private Paint r1Paint; private Paint r2Paint; private Paint r3Paint; - private final RectF indexRect = new RectF(); + private final RectFloat indexRect = new RectFloat(); private AbstractRender render; private float[] xPointBuffer = new float[4]; @@ -52,41 +56,48 @@ public class RSIDrawing implements IDrawing { private float[] gridBuffer = new float[2]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); if (r1Paint == null) { - r1Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r1Paint.setStyle(Paint.Style.STROKE); + r1Paint = new Paint(); + r1Paint.setAntiAlias(true); + r1Paint.setStyle(Paint.Style.STROKE_STYLE); } if (r2Paint == null) { - r2Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r2Paint.setStyle(Paint.Style.STROKE); + r2Paint = new Paint(); + r2Paint.setAntiAlias(true); + r2Paint.setStyle(Paint.Style.STROKE_STYLE); } if (r3Paint == null) { - r3Paint = new Paint(Paint.ANTI_ALIAS_FLAG); - r3Paint.setStyle(Paint.Style.STROKE); + r3Paint = new Paint(); + r3Paint.setAntiAlias(true); + r3Paint.setStyle(Paint.Style.STROKE_STYLE); } r1Paint.setStrokeWidth(sizeColor.getRsiLineSize()); r2Paint.setStrokeWidth(sizeColor.getRsiLineSize()); r3Paint.setStrokeWidth(sizeColor.getRsiLineSize()); - r1Paint.setColor(sizeColor.getRsi1LineColor()); - r2Paint.setColor(sizeColor.getRsi2LineColor()); - r3Paint.setColor(sizeColor.getRsi3LineColor()); + r1Paint.setColor(new Color(sizeColor.getRsi1LineColor())); + r2Paint.setColor(new Color(sizeColor.getRsi2LineColor())); + r3Paint.setColor(new Color(sizeColor.getRsi3LineColor())); - indexRect.set(contentRect); + indexRect.left = contentRect.left; + indexRect.top = contentRect.top; + indexRect.bottom = contentRect.bottom; + indexRect.right = contentRect.right; } @Override diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/StockIndexYLabelDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/StockIndexYLabelDrawing.java index e2906c754f4e533c5208ecb50a7ca0f74eb83725..829d4d3de223b572c50acd152ebdd3f1eb282f9d 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/StockIndexYLabelDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/StockIndexYLabelDrawing.java @@ -18,13 +18,16 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.align.YLabelAlign; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; import java.text.DecimalFormat; @@ -33,34 +36,45 @@ import java.text.DecimalFormat; *

Date: 2017/3/28

* * @author afon + * @since 2021-05-09 */ - public class StockIndexYLabelDrawing implements IDrawing { - - private Paint yLabelPaint; // Y 轴标签的画笔 - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); // 用于 labelPaint 计算文字位置 + /** + * Y 轴标签的画笔 + */ + private Paint yLabelPaint; + /** + * 用于 labelPaint 计算文字位置 + */ + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0, 0, 0, 0, 0); private final DecimalFormat decimalFormatter = new DecimalFormat("0.00"); - private final RectF indexRect = new RectF(); - - private YLabelAlign yLabelAlign; // Y 轴标签对齐方向 + private final RectFloat indexRect = new RectFloat(); + /** + * Y 轴标签对齐方向 + */ + private YLabelAlign yLabelAlign; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { final SizeColor sizeColor = render.getSizeColor(); if (yLabelPaint == null) { - yLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - yLabelPaint.setTextSize(sizeColor.getYLabelSize()); + yLabelPaint = new Paint(); + yLabelPaint.setAntiAlias(true); + yLabelPaint.setTextSize(StringUtils.floatToInt(sizeColor.getYLabelSize())); } - yLabelPaint.setColor(sizeColor.getYLabelColor()); - yLabelPaint.getFontMetrics(fontMetrics); + yLabelPaint.setColor(new Color(sizeColor.getYLabelColor())); + yLabelPaint.getFontMetrics(); yLabelAlign = sizeColor.getYLabelAlign(); if (yLabelAlign == YLabelAlign.RIGHT) { - yLabelPaint.setTextAlign(Paint.Align.RIGHT); + yLabelPaint.setTextAlign(TextAlignment.RIGHT); } - indexRect.set(contentRect); + indexRect.left = contentRect.left; + indexRect.top = contentRect.top; + indexRect.bottom = contentRect.bottom; + indexRect.right = contentRect.right; } @Override @@ -72,17 +86,8 @@ public class StockIndexYLabelDrawing implements IDrawing { public void onComputeOver(Canvas canvas, int minIndex, int maxIndex, float minY, float maxY) { float labelX = yLabelAlign == YLabelAlign.LEFT ? indexRect.left + 5 : indexRect.right - 5; - canvas.drawText( - decimalFormatter.format(maxY), - labelX, - indexRect.top - fontMetrics.top, - yLabelPaint); - - canvas.drawText( - decimalFormatter.format(minY), - labelX, - indexRect.bottom - fontMetrics.bottom, - yLabelPaint); + canvas.drawText(yLabelPaint, decimalFormatter.format(maxY), labelX, indexRect.top - fontMetrics.top); + canvas.drawText(yLabelPaint, decimalFormatter.format(minY), labelX, indexRect.bottom - fontMetrics.bottom); } @Override diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineDrawing.java index 5b1478e8bda18b28b87aac12c8b3ff596829cbb7..339d358de1ad20feaa26229a245ab66bd42354cc 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineDrawing.java @@ -18,45 +18,53 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; + /** *

TimeLineDrawing

*

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public class TimeLineDrawing implements IDrawing { private Paint linePaint; - - private final RectF chartRect = new RectF(); // 分时图显示区域 + /** + * 分时图显示区域 + */ + private final RectFloat chartRect = new RectFloat(); private AbstractRender render; private float[] lineBuffer = new float[4]; private float[] pointBuffer = new float[2]; @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (linePaint == null) { - linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - linePaint.setStyle(Paint.Style.FILL); + linePaint = new Paint(); + linePaint.setAntiAlias(true); + linePaint.setStyle(Paint.Style.FILL_STYLE); } linePaint.setStrokeWidth(sizeColor.getTimeLineSize()); - linePaint.setColor(sizeColor.getTimeLineColor()); + linePaint.setColor(new Color(sizeColor.getTimeLineColor())); - chartRect.set(contentRect); + chartRect.left = contentRect.left; + chartRect.top = contentRect.top; + chartRect.bottom = contentRect.bottom; + chartRect.right = contentRect.right; } @Override @@ -92,7 +100,9 @@ public class TimeLineDrawing implements IDrawing { canvas.drawLines(lineBuffer, 0, count, linePaint); } - // 计算高亮坐标 + /** + * 计算高亮坐标 + */ if (render.isHighlight()) { final EntrySet entrySet = render.getEntrySet(); final int lastEntryIndex = entrySet.getEntryList().size() - 2; @@ -101,10 +111,10 @@ public class TimeLineDrawing implements IDrawing { render.invertMapPoints(pointBuffer); final int highlightIndex = pointBuffer[0] < 0 ? 0 : (int) pointBuffer[0]; final int i = highlightIndex - minIndex; - highlightPoint[0] = highlightIndex < lastEntryIndex ? - lineBuffer[i * 4 + 0] : lineBuffer[lastEntryIndex * 4 + 2]; - highlightPoint[1] = highlightIndex < lastEntryIndex ? - lineBuffer[i * 4 + 1] : lineBuffer[lastEntryIndex * 4 + 3]; + highlightPoint[0] = highlightIndex < lastEntryIndex + ? lineBuffer[i * 4 + 0] : lineBuffer[lastEntryIndex * 4 + 2]; + highlightPoint[1] = highlightIndex < lastEntryIndex + ? lineBuffer[i * 4 + 1] : lineBuffer[lastEntryIndex * 4 + 3]; } canvas.restore(); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineGridAxisDrawing.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineGridAxisDrawing.java index 8b48f1f4fc46eee79b8439f1ffea5cf65a2fe951..978f6f7e71be19d40a37f1211861e30b4a2758b4 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineGridAxisDrawing.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/drawing/TimeLineGridAxisDrawing.java @@ -18,13 +18,17 @@ package com.wordplat.ikvstockchart.drawing; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; + +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; import java.text.DecimalFormat; @@ -33,18 +37,34 @@ import java.text.DecimalFormat; *

Date: 2017/3/10

* * @author afon + * @since 2021-05-09 */ - public class TimeLineGridAxisDrawing implements IDrawing { - - private Paint xLabelPaint; // X 轴标签的画笔 - private Paint yLabelPaint; // Y 轴标签的画笔 - private Paint axisPaint; // X 轴和 Y 轴的画笔 - private Paint gridPaint; // k线图网格线画笔 - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); // 用于 labelPaint 计算文字位置 + /** + * X 轴标签的画笔 + */ + private Paint xLabelPaint; + /** + * Y 轴标签的画笔 + */ + private Paint yLabelPaint; + /** + * X 轴和 Y 轴的画笔 + */ + private Paint axisPaint; + /** + * k线图网格线画笔 + */ + private Paint gridPaint; + /** + * 用于 labelPaint 计算文字位置 + */ + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0, 0, 0, 0, 0); private final DecimalFormat decimalFormatter = new DecimalFormat("0.00"); - - private final RectF chartRect = new RectF(); // 分时图显示区域 + /** + * 分时图显示区域 + */ + private final RectFloat chartRect = new RectFloat(); private AbstractRender render; private SizeColor sizeColor; @@ -54,44 +74,51 @@ public class TimeLineGridAxisDrawing implements IDrawing { private float lineWidth; private int entrySetSize; - + @Override - public void onInit(RectF contentRect, AbstractRender render) { + public void onInit(RectFloat contentRect, AbstractRender render) { this.render = render; this.sizeColor = render.getSizeColor(); if (xLabelPaint == null) { - xLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + xLabelPaint = new Paint(); + xLabelPaint.setAntiAlias(true); } - xLabelPaint.setTextSize(sizeColor.getXLabelSize()); - xLabelPaint.setColor(sizeColor.getXLabelColor()); - xLabelPaint.getFontMetrics(fontMetrics); + xLabelPaint.setTextSize(StringUtils.floatToInt(sizeColor.getXLabelSize())); + xLabelPaint.setColor(new Color(sizeColor.getXLabelColor())); + xLabelPaint.getFontMetrics(); if (yLabelPaint == null) { - yLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + yLabelPaint = new Paint(); + yLabelPaint.setAntiAlias(true); } - yLabelPaint.setTextSize(sizeColor.getYLabelSize()); + yLabelPaint.setTextSize(StringUtils.floatToInt(sizeColor.getYLabelSize())); if (axisPaint == null) { - axisPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - axisPaint.setStyle(Paint.Style.STROKE); + axisPaint = new Paint(); + axisPaint.setAntiAlias(true); + axisPaint.setStyle(Paint.Style.STROKE_STYLE); } if (gridPaint == null) { - gridPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - gridPaint.setStyle(Paint.Style.STROKE); + gridPaint = new Paint(); + gridPaint.setAntiAlias(true); + gridPaint.setStyle(Paint.Style.STROKE_STYLE); } axisPaint.setStrokeWidth(sizeColor.getAxisSize()); - axisPaint.setColor(sizeColor.getAxisColor()); + axisPaint.setColor(new Color(sizeColor.getAxisColor())); gridPaint.setStrokeWidth(sizeColor.getGridSize()); - gridPaint.setColor(sizeColor.getGridColor()); + gridPaint.setColor(new Color(sizeColor.getGridColor())); - chartRect.set(contentRect); + chartRect.left = contentRect.left; + chartRect.top = contentRect.top; + chartRect.bottom = contentRect.bottom; + chartRect.right = contentRect.right; - lineHeight = chartRect.height() / 4; - lineWidth = chartRect.width() / 4; + lineHeight = chartRect.getHeight() / 4; + lineWidth = chartRect.getWidth() / 4; } @Override @@ -103,35 +130,42 @@ public class TimeLineGridAxisDrawing implements IDrawing { public void onComputeOver(Canvas canvas, int minIndex, int maxIndex, float minY, float maxY) { final EntrySet entrySet = render.getEntrySet(); entrySetSize = entrySet.getEntryList().size(); - // 绘制 最外层大框框 + /** + * 绘制 最外层大框框 + */ canvas.drawRect(chartRect, axisPaint); - // 绘制 三条横向网格线 - for (int i = 0 ; i < 3 ; i++) { + /** + * 绘制 三条横向网格线 + */ + for (int i = 0; i < 3; i++) { float lineTop = chartRect.top + (i + 1) * lineHeight; canvas.drawLine(chartRect.left, lineTop, chartRect.right, lineTop, gridPaint); } - for (int i = 0 ; i < 5 ; i++) { + for (int i = 0; i < 5; i++) { float lineLeft = chartRect.left + i * lineWidth; if (i != 0 && i != 4) { - // 绘制 三条竖向网格线 + /** + * 绘制 三条竖向网格线 + */ canvas.drawLine(lineLeft, chartRect.top, lineLeft, chartRect.bottom, gridPaint); - xLabelPaint.setTextAlign(Paint.Align.CENTER); + xLabelPaint.setTextAlign(TextAlignment.CENTER); } else if (i == 0) { - xLabelPaint.setTextAlign(Paint.Align.LEFT); + xLabelPaint.setTextAlign(TextAlignment.LEFT); } else { - xLabelPaint.setTextAlign(Paint.Align.RIGHT); + xLabelPaint.setTextAlign(TextAlignment.RIGHT); } - // 绘制 X 轴 label + /** + * 绘制 X 轴 label + */ if (entrySetSize > 0) { - canvas.drawText( + canvas.drawText(xLabelPaint, entrySet.getEntryList().get(i).getXLabel(), lineLeft, - chartRect.bottom + render.getSizeColor().getXLabelSize(), - xLabelPaint); + chartRect.bottom + render.getSizeColor().getXLabelSize()); } } } @@ -139,10 +173,12 @@ public class TimeLineGridAxisDrawing implements IDrawing { @Override public void onDrawOver(Canvas canvas) { if (entrySetSize < 1) { - return ; + return; } - // 绘制 Y 轴左边 label - for (int i = 0 ; i < 5 ; i++) { + /** + * 绘制 Y 轴左边 label + */ + for (int i = 0; i < 5; i++) { float lineTop = chartRect.top + i * lineHeight; pointCache[1] = lineTop; render.invertMapPoints(pointCache); @@ -157,21 +193,23 @@ public class TimeLineGridAxisDrawing implements IDrawing { } if (i == 2) { - yLabelPaint.setColor(sizeColor.getNeutralColor()); + yLabelPaint.setColor(new Color(sizeColor.getNeutralColor())); } else if (i > 2) { - yLabelPaint.setColor(sizeColor.getDecreasingColor()); + yLabelPaint.setColor(new Color(sizeColor.getNeutralColor())); } else { - yLabelPaint.setColor(sizeColor.getIncreasingColor()); + yLabelPaint.setColor(new Color(sizeColor.getIncreasingColor())); } - yLabelPaint.setTextAlign(Paint.Align.LEFT); + yLabelPaint.setTextAlign(TextAlignment.LEFT); - canvas.drawText(value, chartRect.left + 5, pointCache[0], yLabelPaint); + canvas.drawText(yLabelPaint, value, chartRect.left + 5, pointCache[0]); valueCache[i] = pointCache[1]; } - // 绘制 Y 轴右边 label - for (int i = 0 ; i < 5 ; i++) { + /** + * 绘制 Y 轴右边 label + */ + for (int i = 0; i < 5; i++) { float percent = (valueCache[i] - valueCache[2]) / valueCache[2] * 100; String value = decimalFormatter.format(percent); float lineTop = chartRect.top + i * lineHeight; @@ -185,15 +223,15 @@ public class TimeLineGridAxisDrawing implements IDrawing { } if (i == 2) { - yLabelPaint.setColor(sizeColor.getNeutralColor()); + yLabelPaint.setColor(new Color(sizeColor.getNeutralColor())); } else if (i > 2) { - yLabelPaint.setColor(sizeColor.getDecreasingColor()); + yLabelPaint.setColor(new Color(sizeColor.getDecreasingColor())); } else { - yLabelPaint.setColor(sizeColor.getIncreasingColor()); + yLabelPaint.setColor(new Color(sizeColor.getIncreasingColor())); } - yLabelPaint.setTextAlign(Paint.Align.RIGHT); + yLabelPaint.setTextAlign(TextAlignment.RIGHT); - canvas.drawText(value + "%", chartRect.right - 5, pointCache[0], yLabelPaint); + canvas.drawText(yLabelPaint, value + "%", chartRect.right - 5, pointCache[0]); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/Entry.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/Entry.java index 5ccd7d21a4f84bea2d1b789fbcf926c42ad0babd..4478cd1ce78a787ca3ba4bf75181f7be4b3cff45 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/Entry.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/Entry.java @@ -23,51 +23,92 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/1

* * @author afon + * @since 2021-05-09 */ - public class Entry { - // 初始需全部赋值的属性 - private final float open; // 开盘价 - private final float high; // 最高价 - private final float low; // 最低价 - private final float close; // 收盘价 - private final int volume; // 量 - private String xLabel; // X 轴标签 + /** + * 初始需全部赋值的属性 + */ + /** + * 开盘价 + */ + private final float open; + /** + * 最高价 + */ + private final float high; + /** + * 最低价 + */ + private final float low; + /** + * 收盘价 + */ + private final float close; + /** + * 量 + */ + private final int volume; + /** + * X 轴标签 + */ + private String xLabel; - // MA 指标的三个属性 + /** + * MA 指标的三个属性 + */ private float ma5; private float ma10; private float ma20; - // 量的5日平均和10日平均 + /** + * 量的5日平均和10日平均 + */ private double volumeMa5; private double volumeMa10; - // MACD 指标的三个属性 + /** + * MACD 指标的三个属性 + */ private float dea; private float diff; private float macd; - // KDJ 指标的三个属性 + /** + * KDJ 指标的三个属性 + */ private float k; private float d; private float j; - // RSI 指标的三个属性 + /** + * RSI 指标的三个属性 + */ private float rsi1; private float rsi2; private float rsi3; - // BOLL 指标的三个属性 - private float up; // 上轨线 - private float mb; // 中轨线 - private float dn; // 下轨线 + /** + * BOLL 指标的三个属性 + */ + /** + * 上轨线 + */ + private float up; + /** + * 中轨线 + */ + private float mb; + /** + * 下轨线 + */ + private float dn; /** * 自定义分时图用的数据 * - * @param close 收盘价 + * @param close 收盘价 * @param volume 量 * @param xLabel X 轴标签 */ @@ -83,10 +124,10 @@ public class Entry { /** * 自定义 K 线图用的数据 * - * @param open 开盘价 - * @param high 最高价 - * @param low 最低价 - * @param close 收盘价 + * @param open 开盘价 + * @param high 最高价 + * @param low 最低价 + * @param close 收盘价 * @param volume 量 * @param xLabel X 轴标签 */ @@ -265,11 +306,7 @@ public class Entry { @Override public String toString() { - return "Entry{" + - "open=" + open + - ", high=" + high + - ", low=" + low + - ", close=" + close + - '}'; + return "Entry{" + + "open=" + open + ", high=" + high + ", low=" + low + ", close=" + close + '}'; } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntryAdapter.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntryAdapter.java deleted file mode 100644 index 2da40d35c5c8c2dff3fd4c7b76924378543eeb70..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntryAdapter.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.wordplat.ikvstockchart.entry; - -import android.database.Observable; - -/** - *

EntryAdapter,未完成的代码

- *

Date: 2017/6/7

- * - * @author afon - */ - -public class EntryAdapter { - - private final AdapterDataObservable observable = new AdapterDataObservable(); - - /** - * Returns the total number of items in the data set held by the adapter. - * - * @return The total number of items in this adapter. - */ - public int getItemCount() { - return 0; - } - - /** - * Returns true if one or more observers are attached to this adapter. - * - * @return true if this adapter has observers - */ - public final boolean hasObservers() { - return observable.hasObservers(); - } - - /** - * Register a new observer to listen for data changes. - * - * @param observer Observer to register - */ - public void registerAdapterDataObserver(AdapterDataObserver observer) { - observable.registerObserver(observer); - } - - /** - * Unregister an observer currently listening for data changes. - * - * @param observer Observer to unregister - * - * @see #registerAdapterDataObserver - */ - public void unregisterAdapterDataObserver(AdapterDataObserver observer) { - observable.unregisterObserver(observer); - } - - /** - * Notify any registered observers that the data set has changed. - * - * @see #notifyItemChanged(int) - * @see #notifyItemInserted(int) - * @see #notifyItemRemoved(int) - * @see #notifyItemRangeChanged(int, int) - * @see #notifyItemRangeInserted(int, int) - * @see #notifyItemRangeRemoved(int, int) - */ - public final void notifyDataSetChanged() { - observable.notifyChanged(); - } - - /** - * Notify any registered observers that the item at position has changed. - * Equivalent to calling notifyItemChanged(position, null);. - * - * @param position Position of the item that has changed - * - * @see #notifyItemRangeChanged(int, int) - */ - public final void notifyItemChanged(int position) { - observable.notifyItemRangeChanged(position, 1); - } - - /** - * Notify any registered observers that the item at position has changed with an - * optional payload object. - * - * @param position Position of the item that has changed - * @param payload Optional parameter, use null to identify a "full" update - * - * @see #notifyItemRangeChanged(int, int) - */ - public final void notifyItemChanged(int position, Object payload) { - observable.notifyItemRangeChanged(position, 1, payload); - } - - /** - * Notify any registered observers that the itemCount items starting at - * position positionStart have changed. - * Equivalent to calling notifyItemRangeChanged(position, itemCount, null);. - * - * @param positionStart Position of the first item that has changed - * @param itemCount Number of items that have changed - * - * @see #notifyItemChanged(int) - */ - public final void notifyItemRangeChanged(int positionStart, int itemCount) { - observable.notifyItemRangeChanged(positionStart, itemCount); - } - - /** - * Notify any registered observers that the itemCount items starting at - * position positionStart have changed. An optional payload can be - * passed to each changed item. - * - * @param positionStart Position of the first item that has changed - * @param itemCount Number of items that have changed - * @param payload Optional parameter, use null to identify a "full" update - * - * @see #notifyItemChanged(int) - */ - public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { - observable.notifyItemRangeChanged(positionStart, itemCount, payload); - } - - /** - * Notify any registered observers that the item reflected at position - * has been newly inserted. The item previously at position is now at - * position position + 1. - * - * @param position Position of the newly inserted item in the data set - * - * @see #notifyItemRangeInserted(int, int) - */ - public final void notifyItemInserted(int position) { - observable.notifyItemRangeInserted(position, 1); - } - - /** - * Notify any registered observers that the item reflected at fromPosition - * has been moved to toPosition. - * - * @param fromPosition Previous position of the item. - * @param toPosition New position of the item. - */ - public final void notifyItemMoved(int fromPosition, int toPosition) { - observable.notifyItemMoved(fromPosition, toPosition); - } - - /** - * Notify any registered observers that the currently reflected itemCount - * items starting at positionStart have been newly inserted. The items - * previously located at positionStart and beyond can now be found starting - * at position positionStart + itemCount. - * - * @param positionStart Position of the first item that was inserted - * @param itemCount Number of items inserted - * - * @see #notifyItemInserted(int) - */ - public final void notifyItemRangeInserted(int positionStart, int itemCount) { - observable.notifyItemRangeInserted(positionStart, itemCount); - } - - /** - * Notify any registered observers that the item previously located at position - * has been removed from the data set. The items previously located at and after - * position may now be found at oldPosition - 1. - * - * @param position Position of the item that has now been removed - * - * @see #notifyItemRangeRemoved(int, int) - */ - public final void notifyItemRemoved(int position) { - observable.notifyItemRangeRemoved(position, 1); - } - - /** - * Notify any registered observers that the itemCount items previously - * located at positionStart have been removed from the data set. The items - * previously located at and after positionStart + itemCount may now be found - * at oldPosition - itemCount. - * - * @param positionStart Previous position of the first item that was removed - * @param itemCount Number of items removed from the data set - */ - public final void notifyItemRangeRemoved(int positionStart, int itemCount) { - observable.notifyItemRangeRemoved(positionStart, itemCount); - } - - /** - * Observer base class for watching changes to an adapter. - */ - public static abstract class AdapterDataObserver { - public void onChanged() { - // Do nothing - } - - public void onItemRangeChanged(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { - // fallback to onItemRangeChanged(positionStart, itemCount) if app - // does not override this method. - onItemRangeChanged(positionStart, itemCount); - } - - public void onItemRangeInserted(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeRemoved(int positionStart, int itemCount) { - // do nothing - } - - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - // do nothing - } - } - - static class AdapterDataObservable extends Observable { - public boolean hasObservers() { - return !mObservers.isEmpty(); - } - - public void notifyChanged() { - // since onChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onChanged(); - } - } - - public void notifyItemRangeChanged(int positionStart, int itemCount) { - notifyItemRangeChanged(positionStart, itemCount, null); - } - - public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { - // since onItemRangeChanged() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); - } - } - - public void notifyItemRangeInserted(int positionStart, int itemCount) { - // since onItemRangeInserted() is implemented by the app, it could do anything, - // including removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeInserted(positionStart, itemCount); - } - } - - public void notifyItemRangeRemoved(int positionStart, int itemCount) { - // since onItemRangeRemoved() is implemented by the app, it could do anything, including - // removing itself from {@link mObservers} - and that could cause problems if - // an iterator is used on the ArrayList {@link mObservers}. - // to avoid such problems, just march thru the list in the reverse order. - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); - } - } - - public void notifyItemMoved(int fromPosition, int toPosition) { - for (int i = mObservers.size() - 1; i >= 0; i--) { - mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); - } - } - } -} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntrySet.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntrySet.java index fcd401415476e7d515a7ce2504e86de315fcc406..d333a9a488a6170416eb502635ef4283ed935460 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntrySet.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/EntrySet.java @@ -26,8 +26,8 @@ import java.util.List; *

Date: 2017/3/1

* * @author afon + * @since 2021-05-09 */ - public class EntrySet { /** @@ -131,6 +131,11 @@ public class EntrySet { this.highlightIndex = highlightIndex; } + /** + * getHighlightEntry + * + * @return Entry + */ public Entry getHighlightEntry() { if (0 < highlightIndex && highlightIndex < entries.size()) { return entries.get(highlightIndex); @@ -194,7 +199,10 @@ public class EntrySet { if (end < 2 || end >= entries.size()) { endValue = entries.size() - 1; } else { - endValue = end - 1; // 减去 1 是为了把边缘的 entry 排除 + /** + * 减去 1 是为了把边缘的 entry 排除 + */ + endValue = end - 1; } minY = Float.MAX_VALUE; @@ -318,15 +326,19 @@ public class EntrySet { ema12 = entry.getClose(); ema26 = entry.getClose(); } else { - // EMA(12) = 前一日EMA(12) X 11/13 + 今日收盘价 X 2/13 - // EMA(26) = 前一日EMA(26) X 25/27 + 今日收盘价 X 2/27 + /** + * EMA(12) = 前一日EMA(12) X 11/13 + 今日收盘价 X 2/13 + * EMA(26) = 前一日EMA(26) X 25/27 + 今日收盘价 X 2/27 + */ ema12 = ema12 * 11f / 13f + entry.getClose() * 2f / 13f; ema26 = ema26 * 25f / 27f + entry.getClose() * 2f / 27f; } - // DIF = EMA(12) - EMA(26) 。 - // 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10) - // 用(DIF-DEA)*2 即为 MACD 柱状图。 + /** + * // DIF = EMA(12) - EMA(26) 。 + * 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10) + * 用(DIF-DEA)*2 即为 MACD 柱状图。 + */ diff = ema12 - ema26; dea = dea * 8f / 10f + diff * 2f / 10f; macd = (diff - dea) * 2f; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/SizeColor.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/SizeColor.java index e323ba81c173de3d688b38a92973f0fc814c94ab..53f7eb3f31b3d6f93f2db92cac7429b7cf53c6e8 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/SizeColor.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/SizeColor.java @@ -18,36 +18,188 @@ package com.wordplat.ikvstockchart.entry; -import android.graphics.Paint; import com.wordplat.ikvstockchart.align.XMarkerAlign; import com.wordplat.ikvstockchart.align.YLabelAlign; import com.wordplat.ikvstockchart.align.YMarkerAlign; +import ohos.agp.render.Paint; /** *

线条大小、颜色配置类

*

Date: 2017/3/8

* * @author afon + * @since 2021-05-09 */ - public class SizeColor { - - /////////////////////////////////////////////////////////////////////////// - // 与轴、网格有关的属性和方法 - /////////////////////////////////////////////////////////////////////////// - - private float xLabelSize = 21; // X 轴标签字符大小 - private int xLabelColor = 0xff282b34; // X 轴标签字符颜色 - private float xLabelViewHeight = 50; // X 轴 Label 区域的高度 - private float yLabelSize = 21; // Y 轴标签字符大小 - private int yLabelColor = 0xff282b34; // Y 轴标签字符颜色 - private YLabelAlign yLabelAlign = YLabelAlign.LEFT; // Y 轴标签对齐方向 - - private float axisSize = 2f; // 轴线条大小 - private int axisColor = 0xffdddddd; // 轴线条颜色 - private float gridSize = 2f; // 网格线大小 - private int gridColor = 0xffdddddd; // 网格线颜色 + /** + * 与股票指标有关的属性和方法 + */ + /** + * MA 平均线大小 + */ + private float maLineSize = 2f; + /** + * MA5 平均线颜色 + */ + private int ma5Color = 0xff82b1ff; + /** + * MA10 平均线颜色 + */ + private int ma10Color = 0xffffab40; + /** + * MA20 平均线颜色 + */ + private int ma20Color = 0xfff06292; + /** + * BOLL 线条大小 + */ + private float bollLineSize = 2f; + /** + * BOLL MID 线条颜色 + */ + private int bollMidLineColor = 0xff82b1ff; + /** + * BOLL UPPER 线条颜色 + */ + private int bollUpperLineColor = 0xffffab40; + /** + * BOLL LOWER 线条颜色 + */ + private int bollLowerLineColor = 0xfff06292; + /** + * KDJ 线条大小 + */ + private float kdjLineSize = 2f; + /** + * KDJ K 线条颜色 + */ + private int kdjKLineColor = 0xff82b1ff; + /** + * KDJ D 线条颜色 + */ + private int kdjDLineColor = 0xffffab40; + /** + * KDJ J 线条颜色 + */ + private int kdjJLineColor = 0xfff06292; + /** + * MACD 两条线大小 + */ + private float macdLineSize = 2f; + /** + * 高亮的 MACD 字符颜色,此值与 macdTextColor 不同 + */ + private int macdHighlightTextColor = 0xfff06292; + /** + * DEA 线条颜色 + */ + private int deaLineColor = 0xff82b1ff; + /** + * DIFF 线条颜色 + */ + private int diffLineColor = 0xffffab40; + /** + * RSI 线条大小 + */ + private float rsiLineSize = 2f; + /** + * RSI 第一条线颜色 + */ + private int rsi1LineColor = 0xff82b1ff; + /** + * RSI 第二条线颜色 + */ + private int rsi2LineColor = 0xffffab40; + /** + * RSI 第三条线颜色 + */ + private int rsi3LineColor = 0xfff06292; + /** + * MA 字符大小 + */ + private float maTextSize = 21; + /** + * MA 字符颜色 + */ + private int maTextColor = 0xff282b34; + /** + * BOLL 字符大小 + */ + private float bollTextSize = 21; + /** + * BOLL 字符颜色 + */ + private int bollTextColor = 0xff282b34; + /** + * KDJ 字符大小 + */ + private float kdjTextSize = 21; + /** + * KDJ 字符颜色 + */ + private int kdjTextColor = 0xff282b34; + /** + * MACD 字符大小 + */ + private float macdTextSize = 21; + /** + * MACD 字符颜色 + */ + private int macdTextColor = 0xff282b34; + /** + * RSI 字符大小 + */ + private float rsiTextSize = 21; + /** + * RSI 字符颜色 + */ + private int rsiTextColor = 0xff282b34; + /** + * 与轴、网格有关的属性和方法 + */ + + /** + * X 轴标签字符大小 + */ + private float xLabelSize = 21; + /** + * X 轴标签字符颜色 + */ + private int xLabelColor = 0xff282b34; + /** + * X 轴 Label 区域的高度 + */ + private float xLabelViewHeight = 100; + /** + * Y 轴标签字符大小 + */ + private float yLabelSize = 21; + /** + * Y 轴标签字符颜色 + */ + private int yLabelColor = 0xff282b34; + /** + * Y 轴标签对齐方向 + */ + private YLabelAlign yLabelAlign = YLabelAlign.LEFT; + + /** + * 轴线条大小 + */ + private float axisSize = 2f; + /** + * 轴线条颜色 + */ + private int axisColor = 0xffdddddd; + /** + * 网格线大小 + */ + private float gridSize = 2f; + /** + * 网格线颜色 + */ + private int gridColor = 0xffdddddd; public float getXLabelSize() { return xLabelSize; @@ -129,19 +281,41 @@ public class SizeColor { this.gridColor = gridColor; } - /////////////////////////////////////////////////////////////////////////// - // 与高亮、MarkerView 有关的属性和方法 - /////////////////////////////////////////////////////////////////////////// - - private float highlightSize = 2f; // 高亮线条大小 - private int highlightColor = 0xff282b34; // 高亮线条颜色 0xff1c232e - - private float markerBorderSize = 2f; // MarkerView 边框大小 - private int markerBorderColor = 0xff282b34; // MarkerView 边框颜色 - private float markerTextSize = 21; // MarkerView 字符大小 - private int markerTextColor = 0xff282b34; // MarkerView 字符颜色 - private XMarkerAlign xMarkerAlign = XMarkerAlign.AUTO; // X 轴 MarkerView 对齐方向 - private YMarkerAlign yMarkerAlign = YMarkerAlign.AUTO; // Y 轴 MarkerView 对齐方向 + /** + * 与高亮、MarkerView 有关的属性和方法 + */ + /** + * 高亮线条大小 + */ + private float highlightSize = 2f; + /** + * 高亮线条颜色 0xff1c232e + */ + private int highlightColor = 0xff282b34; + /** + * MarkerView 边框大小 + */ + private float markerBorderSize = 2f; + /** + * MarkerView 边框颜色 + */ + private int markerBorderColor = 0xff282b34; + /** + * MarkerView 字符大小 + */ + private float markerTextSize = 21; + /** + * MarkerView 字符颜色 + */ + private int markerTextColor = 0xff282b34; + /** + * X 轴 MarkerView 对齐方向 + */ + private XMarkerAlign xMarkerAlign = XMarkerAlign.AUTO; + /** + * Y 轴 MarkerView 对齐方向 + */ + private YMarkerAlign yMarkerAlign = YMarkerAlign.AUTO; public float getHighlightSize() { return highlightSize; @@ -207,13 +381,21 @@ public class SizeColor { this.yMarkerAlign = yMarkerAlign; } - /////////////////////////////////////////////////////////////////////////// - // 与分时图有关的属性和方法 - /////////////////////////////////////////////////////////////////////////// - - private float timeLineSize = 2f; // 分时线大小 - private int timeLineColor = 0xff82b1ff; // 分时线颜色 - private int timeLineMaxCount = 240; // 分时图 entry 最多个数。注:此值与 entrySet 里的 entries.size() 意义不同,这里指 X 轴上最多能容纳多少个 entry + /** + * 与分时图有关的属性和方法 + */ + /** + * 分时线大小 + */ + private float timeLineSize = 2f; + /** + * 分时线颜色 + */ + private int timeLineColor = 0xff82b1ff; + /** + * 分时图 entry 最多个数。注:此值与 entrySet 里的 entries.size() 意义不同,这里指 X 轴上最多能容纳多少个 entry + */ + private int timeLineMaxCount = 240; public float getTimeLineSize() { return timeLineSize; @@ -239,25 +421,57 @@ public class SizeColor { this.timeLineMaxCount = timeLineMaxCount; } - /////////////////////////////////////////////////////////////////////////// - // 与蜡烛图有关的属性和方法 - /////////////////////////////////////////////////////////////////////////// - - private float candleBorderSize = 3f; // 蜡烛图矩形边框大小 - private float candleExtremumLabelSize = 21; // 蜡烛图极值字符大小 - private int candleExtremumLableColor = 0xff282b34; // 蜡烛图极值字符颜色 - - private float shadowSize = 4f; // 影线大小 - private int increasingColor = 0xffe05959; // 上涨颜色 - private int decreasingColor = 0xff4fb86a; // 下跌颜色 - private int neutralColor = 0xff282b34; // 不涨不跌颜色 - - private int portraitDefaultVisibleCount = 50; // 竖屏状态下的默认缩放倍数下显示多少个蜡烛图。注:横屏时会自动根据视图宽高变化比例计算,不需要手工设置 - private int zoomInTimes = 3; // 最多放大次数 - private int zoomOutTimes = 3; // 最多缩小次数 - - private Paint.Style increasingStyle = Paint.Style.FILL; // 上涨蜡烛图填充样式。默认实心 - private Paint.Style decreasingStyle = Paint.Style.STROKE; // 下跌蜡烛图填充样式,默认实心 + /** + * 与蜡烛图有关的属性和方法 + */ + /** + * 蜡烛图矩形边框大小 + */ + private float candleBorderSize = 3f; + /** + * 蜡烛图极值字符大小 + */ + private float candleExtremumLabelSize = 21; + /** + * 蜡烛图极值字符颜色 + */ + private int candleExtremumLableColor = 0xff282b34; + /** + * 影线大小 + */ + private float shadowSize = 4f; + /** + * 上涨颜色 + */ + private int increasingColor = 0xffe05959; + /** + * 下跌颜色 + */ + private int decreasingColor = 0xff4fb86a; + /** + * 不涨不跌颜色 + */ + private int neutralColor = 0xff282b34; + /** + * 竖屏状态下的默认缩放倍数下显示多少个蜡烛图。注:横屏时会自动根据视图宽高变化比例计算,不需要手工设置 + */ + private int portraitDefaultVisibleCount = 50; + /** + * 最多放大次数 + */ + private int zoomInTimes = 3; + /** + * 最多缩小次数 + */ + private int zoomOutTimes = 3; + /** + * 上涨蜡烛图填充样式。默认实心 + */ + private Paint.Style increasingStyle = Paint.Style.FILL_STYLE; + /** + * 下跌蜡烛图填充样式,默认实心 + */ + private Paint.Style decreasingStyle = Paint.Style.STROKE_STYLE; public float getCandleBorderSize() { return candleBorderSize; @@ -355,49 +569,7 @@ public class SizeColor { this.decreasingStyle = decreasingStyle; } - /////////////////////////////////////////////////////////////////////////// - // 与股票指标有关的属性和方法 - /////////////////////////////////////////////////////////////////////////// - private float maLineSize = 2f; // MA 平均线大小 - private int ma5Color = 0xff82b1ff; // MA5 平均线颜色 - private int ma10Color = 0xffffab40; // MA10 平均线颜色 - private int ma20Color = 0xfff06292; // MA20 平均线颜色 - - private float bollLineSize = 2f; // BOLL 线条大小 - private int bollMidLineColor = 0xff82b1ff; // BOLL MID 线条颜色 - private int bollUpperLineColor = 0xffffab40; // BOLL UPPER 线条颜色 - private int bollLowerLineColor = 0xfff06292; // BOLL LOWER 线条颜色 - - private float kdjLineSize = 2f; // KDJ 线条大小 - private int kdjKLineColor = 0xff82b1ff; // KDJ K 线条颜色 - private int kdjDLineColor = 0xffffab40; // KDJ D 线条颜色 - private int kdjJLineColor = 0xfff06292; // KDJ J 线条颜色 - - private float macdLineSize = 2f; // MACD 两条线大小 - private int macdHighlightTextColor = 0xfff06292; // 高亮的 MACD 字符颜色,此值与 macdTextColor 不同 - private int deaLineColor = 0xff82b1ff; // DEA 线条颜色 - private int diffLineColor = 0xffffab40; // DIFF 线条颜色 - - private float rsiLineSize = 2f; // RSI 线条大小 - private int rsi1LineColor = 0xff82b1ff; // RSI 第一条线颜色 - private int rsi2LineColor = 0xffffab40; // RSI 第二条线颜色 - private int rsi3LineColor = 0xfff06292; // RSI 第三条线颜色 - - private float maTextSize = 21; // MA 字符大小 - private int maTextColor = 0xff282b34; // MA 字符颜色 - - private float bollTextSize = 21; // BOLL 字符大小 - private int bollTextColor = 0xff282b34; // BOLL 字符颜色 - - private float kdjTextSize = 21; // KDJ 字符大小 - private int kdjTextColor = 0xff282b34; // KDJ 字符颜色 - - private float macdTextSize = 21; // MACD 字符大小 - private int macdTextColor = 0xff282b34; // MACD 字符颜色 - - private float rsiTextSize = 21; // RSI 字符大小 - private int rsiTextColor = 0xff282b34; // RSI 字符颜色 public float getMaLineSize() { return maLineSize; @@ -639,10 +811,9 @@ public class SizeColor { this.rsiTextColor = rsiTextColor; } - /////////////////////////////////////////////////////////////////////////// - // 其它 - /////////////////////////////////////////////////////////////////////////// - + /** + * 其它 + */ private float loadingTextSize = 21; private int loadingTextColor = 0xff282b34; private String loadingText = "Loading..."; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockBOLLIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockBOLLIndex.java index 31669bba7050845f8573e11182fb4d69f7f88163..66b7b10f3e296fc666eacdf99583afee58f4e417 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockBOLLIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockBOLLIndex.java @@ -23,14 +23,20 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/16

* * @author afon + * @since 2021-05-09 */ - public class StockBOLLIndex extends StockIndex { - + /** + * StockBOLLIndex + */ public StockBOLLIndex() { super(STANDARD_HEIGHT); } + /** + * StockBOLLIndex + * @param height + */ public StockBOLLIndex(int height) { super(height); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockDataTest.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockDataTest.java index 5e1ef8d55b4acf100f877745081e4a8cc453013d..1bb370e298f9bbb027e47aa6e39f6b770775d5d7 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockDataTest.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockDataTest.java @@ -23,14 +23,23 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/2

* * @author afon + * @since 2021-05-09 */ - public class StockDataTest { + /** + * KLINE + */ + public static final String KLINE = "15.6|15.6|15.6|15.6|1|2015/10/10,17.16|17.16|17.16|17.16|1|2015/10/11,18.88|18.88|18.88|18.88|1|2015/10/12,20.77|20.77|20.77|20.77|1|2015/10/13,22.85|22.85|22.85|22.85|1|2015/10/14,25.14|25.14|25.14|25.14|1|2015/10/15,27.65|27.65|27.65|27.65|1|2015/10/16,30.42|30.42|30.42|30.42|1|2015/10/17,33.46|33.46|33.46|33.46|1|2015/10/18,36.81|36.81|36.81|36.81|31|2015/10/19,40.49|40.49|40.49|40.49|42|2015/10/20,44.54|44.54|44.54|44.54|4|2015/10/21,48.99|48.99|48.99|48.99|2|2015/10/22,53.89|53.89|53.89|53.89|2|2015/10/23,59.28|59.28|59.28|59.28|2|2015/10/24,65.21|65.21|65.21|65.21|29|2015/10/25,71.73|71.73|71.73|71.73|192|2015/10/26,78.9|78.9|78.9|78.9|100|2015/10/27,86.79|86.79|86.79|86.79|74|2015/10/28,95.47|95.47|95.47|95.47|48|2015/10/29,105.02|105.02|105.02|105.02|90|2015/10/30,115.52|115.52|115.52|115.52|99|2015/10/31,127.07|127.07|127.07|127.07|38|2015/11/1,139.78|139.78|139.78|139.78|39|2015/11/2,153.76|153.76|153.76|153.76|615|2015/11/3,169.14|169.14|169.14|169.14|35|2015/11/4,186.05|186.05|186.05|186.05|66330|2015/11/5,204.66|204.66|175.0|204.66|4339|2015/11/6,186.05|186.05|184.19|184.19|750|2015/11/9,165.77|165.77|165.77|165.77|378|2015/11/10,149.19|165.77|149.19|150.7|9940|2015/11/11,137.0|149.0|135.63|135.63|1764|2015/11/12,122.07|139.0|122.07|135.49|4834|2015/11/13,138.0|139.0|121.94|121.94|2193|2015/11/14,129.96|134.13|110.1|134.13|21002|2015/11/16,134.8|147.54|134.8|147.54|321|2015/11/17,161.99|162.29|147.01|162.29|30087|2015/11/18,178.0|178.52|146.06|174.02|44307|2015/11/19,157.45|191.42|157.45|191.42|33700|2015/11/20,210.5|210.56|210.5|210.56|234|2015/11/21,210.48|231.62|210.48|231.62|1015|2015/11/23,254.78|254.78|254.78|254.78|758|2015/11/24,280.26|280.26|230.0|280.26|36243|2015/11/25,308.29|308.29|253.23|289.67|93637|2015/11/27,265.0|290.5|264.0|289.0|35870|2015/11/28,266.0|306.0|265.0|274.27|17528|2015/11/29,258.0|301.7|252.0|301.7|7754|2015/11/30,331.87|331.87|279.0|331.87|9287|2015/12/1,365.06|365.06|340.0|365.06|1233|2015/12/2,401.57|401.57|401.57|401.57|18|2015/12/6,441.73|441.73|441.73|441.73|23|2015/12/7,485.9|485.9|485.9|485.9|89|2015/12/8,501.0|528.0|501.0|525.01|15909|2015/12/9,500.0|539.0|500.0|536.39|15616|2015/12/10,510.0|548.0|510.0|544.92|17517|2015/12/11,521.0|537.0|490.43|490.43|10533|2015/12/12,516.0|518.0|441.39|441.39|2359|2015/12/14,400.0|446.0|397.25|445.0|7275|2015/12/15,415.0|431.0|414.0|426.85|5851|2015/12/16,426.0|446.0|426.0|441.9|6047|2015/12/17,431.0|447.0|431.0|443.6|6594|2015/12/18,443.0|459.0|438.0|445.26|6284|2015/12/19,444.0|453.0|439.0|444.8|6322|2015/12/21,445.0|469.0|437.0|463.33|9732|2015/12/22,463.0|478.0|457.0|464.88|9967|2015/12/23,462.0|482.0|462.0|480.55|8664|2015/12/24,479.0|501.0|478.0|491.92|12688|2015/12/25,491.0|498.0|442.73|442.97|26530|2015/12/26,460.0|460.0|398.67|398.67|128|2015/12/28,399.0|438.0|358.8|411.79|24309|2015/12/29,410.0|428.0|370.61|397.29|10939|2015/12/30,397.0|437.02|359.01|435.59|10678|2015/12/31,430.0|479.15|429.01|479.0|8944|2016/1/4,489.99|500.0|432.8|440.05|8979|2016/1/5,441.0|460.0|400.0|435.0|8162|2016/1/6,430.0|440.0|418.0|435.78|7358|2016/1/7,440.0|479.36|437.0|479.36|4628|2016/1/8,480.0|527.3|476.0|527.3|3381|2016/1/9,540.0|580.03|512.0|580.03|1961|2016/1/11,570.0|638.03|570.0|638.03|363|2016/1/12,638.0|701.83|638.0|701.83|198|2016/1/13,772.01|772.01|772.01|772.01|130|2016/1/14,849.21|849.21|849.21|849.21|92|2016/1/15,934.13|934.13|934.12|934.13|659|2016/1/16,1027.54|1027.54|1026.0|1027.46|5332|2016/1/18,1129.0|1130.21|1129.0|1130.21|2743|2016/1/19,1243.23|1243.23|1017.19|1243.23|4401|2016/1/20,1367.55|1367.55|1200.0|1367.55|715|2016/1/21,1290.0|1504.31|1230.79|1504.31|2421|2016/1/22,1370.0|1581.69|1360.0|1427.06|4391|2016/1/23,1284.35|1569.0|1284.35|1293.25|4222|2016/1/25,1200.0|1422.58|1163.92|1419.99|10552|2016/1/26,1278.0|1450.0|1277.99|1277.99|2478|2016/1/27,1150.19|1150.19|1150.19|1150.19|24|2016/1/28,1035.17|1265.21|1035.17|1265.21|597|2016/1/29,1155.0|1155.0|1148.0|1148.31|204|2016/1/30,1159.0|1166.0|1043.0|1166.0|34|2016/2/1,1088.01|1282.0|1088.01|1160.0|7|2016/2/2,1050.0|1276.0|1050.0|1276.0|8|2016/2/3,1403.0|1403.0|1180.1|1180.1|121|2016/2/4,1290.0|1290.0|1290.0|1290.0|1|2016/2/5,1419.0|1419.0|1419.0|1419.0|3|2016/2/15,1560.9|1560.9|1560.9|1560.9|6|2016/2/16,1716.99|1716.99|1716.99|1716.99|1|2016/2/17,1888.69|1888.69|1730.0|1888.69|2489|2016/2/18,2077.56|2077.56|1888.9|2077.5|179|2016/2/19,2285.25|2285.25|2285.25|2285.25|2|2016/2/20,2513.78|2513.78|2513.78|2513.78|1|2016/2/22,2765.16|2765.16|2262.4|2681.24|11969|2016/2/23,2940.0|2949.36|2796.0|2949.36|2594|2016/2/24,3200.0|3244.3|2654.42|3244.3|18336|2016/2/25,3568.73|3568.73|3568.73|3568.73|1|2016/2/26,3300.01|3925.6|3300.0|3925.6|211|2016/2/27,4318.16|4318.16|4318.16|4318.16|2|2016/2/29,4749.98|4749.98|4749.98|4749.98|2|2016/3/1,5224.98|5224.98|4380.0|5222.83|2847|2016/3/2,5000.0|5745.11|5000.0|5560.93|6699|2016/3/3,5200.0|5500.0|5200.0|5247.02|2588|2016/3/4,4999.0|5700.0|4865.0|5100.0|1304|2016/3/5,4800.0|5610.0|4590.0|4993.67|5186|2016/3/7,4725.0|5031.0|4720.0|4974.43|4277|2016/3/8,4680.0|5253.0|4660.0|4937.77|2812|2016/3/9,4690.0|5431.55|4678.0|5431.55|704|2016/3/10,5974.71|5974.71|5974.71|5974.71|19|2016/3/11,6572.18|6572.18|6572.18|6572.18|1|2016/3/12,5914.96|7229.4|5914.96|7229.4|2|2016/3/14,7952.34|7952.34|7952.34|7952.34|51|2016/3/15,8747.57|8747.57|8747.57|8747.57|1|2016/3/16,9622.33|9622.33|9073.0|9180.0|10797|2016/3/17,8262.0|9599.0|8262.0|9060.0|12696|2016/3/18,8154.0|9966.0|8154.0|9966.0|1523|2016/3/19,8969.4|10962.6|8969.4|10962.6|7800|2016/3/21,10000.0|12058.86|9866.34|12058.86|22749|2016/3/22,11000.0|11000.09|10852.97|10852.97|7554|2016/3/23,9767.67|9767.67|9767.67|9767.67|9760|2016/3/24,8790.9|10744.44|8790.9|10744.44|21621|2016/3/25,9673.92|11818.88|9673.92|11818.88|8981|2016/3/26,13000.77|13000.77|13000.77|13000.77|8898|2016/3/28,11700.69|14300.85|11700.69|14300.85|8591|2016/3/29,12880.7|15730.94|12880.7|15730.94|989|2016/3/30,14300.0|16000.0|14157.85|14157.85|8555|2016/3/31,12742.06|14999.0|12742.06|12742.06|8451|2016/4/1,11467.85|12742.06|11467.85|11467.85|7638|2016/4/2,12614.64|12614.64|12600.0|12614.64|9282|2016/4/5,13876.0|13876.1|13876.0|13876.1|6155|2016/4/6,15263.71|15263.71|13877.0|15263.71|7010|2016/4/8,15265.0|16790.08|15263.1|16790.0|1686|2016/4/9,18469.0|18469.0|16791.0|18469.0|6263|2016/4/11,20314.99|20314.99|17140.0|17140.0|9121|2016/4/12,18854.0|18854.0|15426.0|15426.0|6350|2016/4/13,16968.0|16968.0|13883.4|16190.0|5811|2016/4/14,16200.0|16870.0|16200.0|16475.0|6172|2016/4/15,16480.0|16500.0|16480.0|16499.0|1482|2016/4/16,16505.0|16560.0|16350.0|16534.49|17921|2016/4/18,16540.0|16585.0|16000.0|16585.0|12586|2016/4/19,16590.0|17750.0|14926.6|16100.0|6813|2016/4/20,16150.0|16500.0|15900.0|15900.0|6288|2016/4/21,15000.0|15109.0|14310.0|14310.0|847|2016/4/22,14311.0|14311.0|12879.0|12879.0|1711|2016/4/23,11591.1|11591.1|11591.1|11591.1|2|2016/4/25,10431.99|10431.99|10431.99|10431.99|2|2016/4/26,9388.79|9388.79|9388.79|9388.79|227|2016/4/27,10089.0|10327.67|8449.91|10327.67|29609|2016/4/28,9510.0|11200.0|9294.9|9294.9|11540|2016/4/29,8365.41|8365.41|8365.41|8365.41|5362|2016/4/30,7528.87|7528.87|7528.87|7528.87|4|2016/5/3,6775.98|6775.98|6775.98|6775.98|1|2016/5/4,6098.38|7453.58|6098.38|7453.58|13|2016/5/5,6708.32|7221.0|6708.22|6708.22|169|2016/5/6,6666.0|6901.34|6037.4|6888.0|1515|2016/5/7,7499.88|7576.8|6200.0|7576.8|118919|2016/5/9,7500.0|8334.48|7500.0|8334.48|98763|2016/5/10,9167.93|9167.93|7501.03|7501.03|93618|2016/5/11,7425.0|7425.0|6750.93|6750.93|8956|2016/5/12,6682.0|6740.0|6075.84|6690.41|10877|2016/5/13,6700.0|6760.0|6700.0|6760.0|10144|2016/5/14,6800.0|7436.0|6732.0|7436.0|13706|2016/5/16,7441.0|8179.6|7441.0|8179.6|6733|2016/5/17,8015.0|8997.56|8014.0|8995.0|9218|2016/5/18,8815.0|9894.5|8815.0|9893.0|3589|2016/5/19,9898.0|10882.3|9898.0|10874.0|390|2016/5/20,10880.0|11961.4|10877.0|11961.0|824|2016/5/21,11965.0|13157.1|11962.0|13157.0|269|2016/5/23,13025.0|14472.7|13000.0|14472.7|590|2016/5/24,15919.97|15919.97|15919.96|15919.96|2|2016/5/25,17510.99|17510.99|17510.99|17510.99|1|2016/5/26"; + /** + * TIME_LINE + */ + public static final String TIME_LINE = ""; - public final static String KLINE = "15.6|15.6|15.6|15.6|1|2015/10/10,17.16|17.16|17.16|17.16|1|2015/10/11,18.88|18.88|18.88|18.88|1|2015/10/12,20.77|20.77|20.77|20.77|1|2015/10/13,22.85|22.85|22.85|22.85|1|2015/10/14,25.14|25.14|25.14|25.14|1|2015/10/15,27.65|27.65|27.65|27.65|1|2015/10/16,30.42|30.42|30.42|30.42|1|2015/10/17,33.46|33.46|33.46|33.46|1|2015/10/18,36.81|36.81|36.81|36.81|31|2015/10/19,40.49|40.49|40.49|40.49|42|2015/10/20,44.54|44.54|44.54|44.54|4|2015/10/21,48.99|48.99|48.99|48.99|2|2015/10/22,53.89|53.89|53.89|53.89|2|2015/10/23,59.28|59.28|59.28|59.28|2|2015/10/24,65.21|65.21|65.21|65.21|29|2015/10/25,71.73|71.73|71.73|71.73|192|2015/10/26,78.9|78.9|78.9|78.9|100|2015/10/27,86.79|86.79|86.79|86.79|74|2015/10/28,95.47|95.47|95.47|95.47|48|2015/10/29,105.02|105.02|105.02|105.02|90|2015/10/30,115.52|115.52|115.52|115.52|99|2015/10/31,127.07|127.07|127.07|127.07|38|2015/11/1,139.78|139.78|139.78|139.78|39|2015/11/2,153.76|153.76|153.76|153.76|615|2015/11/3,169.14|169.14|169.14|169.14|35|2015/11/4,186.05|186.05|186.05|186.05|66330|2015/11/5,204.66|204.66|175.0|204.66|4339|2015/11/6,186.05|186.05|184.19|184.19|750|2015/11/9,165.77|165.77|165.77|165.77|378|2015/11/10,149.19|165.77|149.19|150.7|9940|2015/11/11,137.0|149.0|135.63|135.63|1764|2015/11/12,122.07|139.0|122.07|135.49|4834|2015/11/13,138.0|139.0|121.94|121.94|2193|2015/11/14,129.96|134.13|110.1|134.13|21002|2015/11/16,134.8|147.54|134.8|147.54|321|2015/11/17,161.99|162.29|147.01|162.29|30087|2015/11/18,178.0|178.52|146.06|174.02|44307|2015/11/19,157.45|191.42|157.45|191.42|33700|2015/11/20,210.5|210.56|210.5|210.56|234|2015/11/21,210.48|231.62|210.48|231.62|1015|2015/11/23,254.78|254.78|254.78|254.78|758|2015/11/24,280.26|280.26|230.0|280.26|36243|2015/11/25,308.29|308.29|253.23|289.67|93637|2015/11/27,265.0|290.5|264.0|289.0|35870|2015/11/28,266.0|306.0|265.0|274.27|17528|2015/11/29,258.0|301.7|252.0|301.7|7754|2015/11/30,331.87|331.87|279.0|331.87|9287|2015/12/1,365.06|365.06|340.0|365.06|1233|2015/12/2,401.57|401.57|401.57|401.57|18|2015/12/6,441.73|441.73|441.73|441.73|23|2015/12/7,485.9|485.9|485.9|485.9|89|2015/12/8,501.0|528.0|501.0|525.01|15909|2015/12/9,500.0|539.0|500.0|536.39|15616|2015/12/10,510.0|548.0|510.0|544.92|17517|2015/12/11,521.0|537.0|490.43|490.43|10533|2015/12/12,516.0|518.0|441.39|441.39|2359|2015/12/14,400.0|446.0|397.25|445.0|7275|2015/12/15,415.0|431.0|414.0|426.85|5851|2015/12/16,426.0|446.0|426.0|441.9|6047|2015/12/17,431.0|447.0|431.0|443.6|6594|2015/12/18,443.0|459.0|438.0|445.26|6284|2015/12/19,444.0|453.0|439.0|444.8|6322|2015/12/21,445.0|469.0|437.0|463.33|9732|2015/12/22,463.0|478.0|457.0|464.88|9967|2015/12/23,462.0|482.0|462.0|480.55|8664|2015/12/24,479.0|501.0|478.0|491.92|12688|2015/12/25,491.0|498.0|442.73|442.97|26530|2015/12/26,460.0|460.0|398.67|398.67|128|2015/12/28,399.0|438.0|358.8|411.79|24309|2015/12/29,410.0|428.0|370.61|397.29|10939|2015/12/30,397.0|437.02|359.01|435.59|10678|2015/12/31,430.0|479.15|429.01|479.0|8944|2016/1/4,489.99|500.0|432.8|440.05|8979|2016/1/5,441.0|460.0|400.0|435.0|8162|2016/1/6,430.0|440.0|418.0|435.78|7358|2016/1/7,440.0|479.36|437.0|479.36|4628|2016/1/8,480.0|527.3|476.0|527.3|3381|2016/1/9,540.0|580.03|512.0|580.03|1961|2016/1/11,570.0|638.03|570.0|638.03|363|2016/1/12,638.0|701.83|638.0|701.83|198|2016/1/13,772.01|772.01|772.01|772.01|130|2016/1/14,849.21|849.21|849.21|849.21|92|2016/1/15,934.13|934.13|934.12|934.13|659|2016/1/16,1027.54|1027.54|1026.0|1027.46|5332|2016/1/18,1129.0|1130.21|1129.0|1130.21|2743|2016/1/19,1243.23|1243.23|1017.19|1243.23|4401|2016/1/20,1367.55|1367.55|1200.0|1367.55|715|2016/1/21,1290.0|1504.31|1230.79|1504.31|2421|2016/1/22,1370.0|1581.69|1360.0|1427.06|4391|2016/1/23,1284.35|1569.0|1284.35|1293.25|4222|2016/1/25,1200.0|1422.58|1163.92|1419.99|10552|2016/1/26,1278.0|1450.0|1277.99|1277.99|2478|2016/1/27,1150.19|1150.19|1150.19|1150.19|24|2016/1/28,1035.17|1265.21|1035.17|1265.21|597|2016/1/29,1155.0|1155.0|1148.0|1148.31|204|2016/1/30,1159.0|1166.0|1043.0|1166.0|34|2016/2/1,1088.01|1282.0|1088.01|1160.0|7|2016/2/2,1050.0|1276.0|1050.0|1276.0|8|2016/2/3,1403.0|1403.0|1180.1|1180.1|121|2016/2/4,1290.0|1290.0|1290.0|1290.0|1|2016/2/5,1419.0|1419.0|1419.0|1419.0|3|2016/2/15,1560.9|1560.9|1560.9|1560.9|6|2016/2/16,1716.99|1716.99|1716.99|1716.99|1|2016/2/17,1888.69|1888.69|1730.0|1888.69|2489|2016/2/18,2077.56|2077.56|1888.9|2077.5|179|2016/2/19,2285.25|2285.25|2285.25|2285.25|2|2016/2/20,2513.78|2513.78|2513.78|2513.78|1|2016/2/22,2765.16|2765.16|2262.4|2681.24|11969|2016/2/23,2940.0|2949.36|2796.0|2949.36|2594|2016/2/24,3200.0|3244.3|2654.42|3244.3|18336|2016/2/25,3568.73|3568.73|3568.73|3568.73|1|2016/2/26,3300.01|3925.6|3300.0|3925.6|211|2016/2/27,4318.16|4318.16|4318.16|4318.16|2|2016/2/29,4749.98|4749.98|4749.98|4749.98|2|2016/3/1,5224.98|5224.98|4380.0|5222.83|2847|2016/3/2,5000.0|5745.11|5000.0|5560.93|6699|2016/3/3,5200.0|5500.0|5200.0|5247.02|2588|2016/3/4,4999.0|5700.0|4865.0|5100.0|1304|2016/3/5,4800.0|5610.0|4590.0|4993.67|5186|2016/3/7,4725.0|5031.0|4720.0|4974.43|4277|2016/3/8,4680.0|5253.0|4660.0|4937.77|2812|2016/3/9,4690.0|5431.55|4678.0|5431.55|704|2016/3/10,5974.71|5974.71|5974.71|5974.71|19|2016/3/11,6572.18|6572.18|6572.18|6572.18|1|2016/3/12,5914.96|7229.4|5914.96|7229.4|2|2016/3/14,7952.34|7952.34|7952.34|7952.34|51|2016/3/15,8747.57|8747.57|8747.57|8747.57|1|2016/3/16,9622.33|9622.33|9073.0|9180.0|10797|2016/3/17,8262.0|9599.0|8262.0|9060.0|12696|2016/3/18,8154.0|9966.0|8154.0|9966.0|1523|2016/3/19,8969.4|10962.6|8969.4|10962.6|7800|2016/3/21,10000.0|12058.86|9866.34|12058.86|22749|2016/3/22,11000.0|11000.09|10852.97|10852.97|7554|2016/3/23,9767.67|9767.67|9767.67|9767.67|9760|2016/3/24,8790.9|10744.44|8790.9|10744.44|21621|2016/3/25,9673.92|11818.88|9673.92|11818.88|8981|2016/3/26,13000.77|13000.77|13000.77|13000.77|8898|2016/3/28,11700.69|14300.85|11700.69|14300.85|8591|2016/3/29,12880.7|15730.94|12880.7|15730.94|989|2016/3/30,14300.0|16000.0|14157.85|14157.85|8555|2016/3/31,12742.06|14999.0|12742.06|12742.06|8451|2016/4/1,11467.85|12742.06|11467.85|11467.85|7638|2016/4/2,12614.64|12614.64|12600.0|12614.64|9282|2016/4/5,13876.0|13876.1|13876.0|13876.1|6155|2016/4/6,15263.71|15263.71|13877.0|15263.71|7010|2016/4/8,15265.0|16790.08|15263.1|16790.0|1686|2016/4/9,18469.0|18469.0|16791.0|18469.0|6263|2016/4/11,20314.99|20314.99|17140.0|17140.0|9121|2016/4/12,18854.0|18854.0|15426.0|15426.0|6350|2016/4/13,16968.0|16968.0|13883.4|16190.0|5811|2016/4/14,16200.0|16870.0|16200.0|16475.0|6172|2016/4/15,16480.0|16500.0|16480.0|16499.0|1482|2016/4/16,16505.0|16560.0|16350.0|16534.49|17921|2016/4/18,16540.0|16585.0|16000.0|16585.0|12586|2016/4/19,16590.0|17750.0|14926.6|16100.0|6813|2016/4/20,16150.0|16500.0|15900.0|15900.0|6288|2016/4/21,15000.0|15109.0|14310.0|14310.0|847|2016/4/22,14311.0|14311.0|12879.0|12879.0|1711|2016/4/23,11591.1|11591.1|11591.1|11591.1|2|2016/4/25,10431.99|10431.99|10431.99|10431.99|2|2016/4/26,9388.79|9388.79|9388.79|9388.79|227|2016/4/27,10089.0|10327.67|8449.91|10327.67|29609|2016/4/28,9510.0|11200.0|9294.9|9294.9|11540|2016/4/29,8365.41|8365.41|8365.41|8365.41|5362|2016/4/30,7528.87|7528.87|7528.87|7528.87|4|2016/5/3,6775.98|6775.98|6775.98|6775.98|1|2016/5/4,6098.38|7453.58|6098.38|7453.58|13|2016/5/5,6708.32|7221.0|6708.22|6708.22|169|2016/5/6,6666.0|6901.34|6037.4|6888.0|1515|2016/5/7,7499.88|7576.8|6200.0|7576.8|118919|2016/5/9,7500.0|8334.48|7500.0|8334.48|98763|2016/5/10,9167.93|9167.93|7501.03|7501.03|93618|2016/5/11,7425.0|7425.0|6750.93|6750.93|8956|2016/5/12,6682.0|6740.0|6075.84|6690.41|10877|2016/5/13,6700.0|6760.0|6700.0|6760.0|10144|2016/5/14,6800.0|7436.0|6732.0|7436.0|13706|2016/5/16,7441.0|8179.6|7441.0|8179.6|6733|2016/5/17,8015.0|8997.56|8014.0|8995.0|9218|2016/5/18,8815.0|9894.5|8815.0|9893.0|3589|2016/5/19,9898.0|10882.3|9898.0|10874.0|390|2016/5/20,10880.0|11961.4|10877.0|11961.0|824|2016/5/21,11965.0|13157.1|11962.0|13157.0|269|2016/5/23,13025.0|14472.7|13000.0|14472.7|590|2016/5/24,15919.97|15919.97|15919.96|15919.96|2|2016/5/25,17510.99|17510.99|17510.99|17510.99|1|2016/5/26"; - - public final static String TIME_LINE = ""; - + /** + * parseKLineData + * @param data + * @return + */ public static EntrySet parseKLineData(String data) { final EntrySet entrySet = new EntrySet(); @@ -52,6 +61,11 @@ public class StockDataTest { return entrySet; } + /** + * parseTimeLine + * @param data + * @return + */ public static EntrySet parseTimeLine(String data) { final EntrySet entrySet = new EntrySet(); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockIndex.java index 9930d547f372c6773418c2a9bd71952294535eba..8f8728fe3d900c5f797a752de519b1df039844a8 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockIndex.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2017 WordPlat Open Source Project * - * https://wordplat.com/InteractiveKLineView/ + * https:wordplat.com/InteractiveKLineView/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http:www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -18,10 +18,10 @@ package com.wordplat.ikvstockchart.entry; -import android.graphics.Matrix; -import android.graphics.RectF; import com.wordplat.ikvstockchart.drawing.IDrawing; +import ohos.agp.utils.Matrix; +import ohos.agp.utils.RectFloat; import java.util.ArrayList; import java.util.List; @@ -31,17 +31,23 @@ import java.util.List; *

Date: 2017/3/14

* * @author afon + * @since 2021-05-09 */ - public abstract class StockIndex { + /** + * 股票指标视图预设高度,单位:像素 + */ + static final int STANDARD_HEIGHT = 300; + /** + * 把值映射到屏幕像素的矩阵 + */ + private final Matrix matrix = new Matrix(); + /** + * 股票指标视图高度,单位:像素 + */ + private final int height; - static final int STANDARD_HEIGHT = 300; // 股票指标视图预设高度,单位:像素 - - private final Matrix matrix = new Matrix(); // 把值映射到屏幕像素的矩阵 - - private final int height; // 股票指标视图高度,单位:像素 - - private final RectF rect = new RectF(); + private final RectFloat rect = new RectFloat(); private final List drawingList = new ArrayList<>(); @@ -75,12 +81,26 @@ public abstract class StockIndex { */ private float extremumYDelta = 0; + /** + * StockIndex + * + * @param height + */ public StockIndex(int height) { this.height = height; } + /** + * computeMinMax + * + * @param currentIndex + * @param entry + */ public abstract void computeMinMax(int currentIndex, Entry entry); + /** + * resetMinMax + */ public void resetMinMax() { minY = Float.MAX_VALUE; maxY = -Float.MAX_VALUE; @@ -94,22 +114,43 @@ public abstract class StockIndex { return height; } - public RectF getRect() { + public RectFloat getRect() { return rect; } + /** + * setRect + * + * @param left + * @param top + * @param right + * @param bottom + */ public void setRect(float left, float top, float right, float bottom) { - rect.set(left, top, right, bottom); + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; } public List getDrawingList() { return drawingList; } + /** + * addDrawing + * + * @param drawing + */ public void addDrawing(IDrawing drawing) { drawingList.add(drawing); } + /** + * removeDrawing + * + * @param drawing + */ public void removeDrawing(IDrawing drawing) { drawingList.remove(drawing); } @@ -146,6 +187,14 @@ public abstract class StockIndex { this.paddingRight = paddingRight; } + /** + * setPadding + * + * @param paddingLeft + * @param paddingTop + * @param paddingRight + * @param paddingBottom + */ public void setPadding(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { this.paddingLeft = paddingLeft; this.paddingTop = paddingTop; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKDJIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKDJIndex.java index b935a8f9fbca56f8dfc20eff3cd5f0c71968bd28..d65e946a397867c23a10cae9d105e751ceb45f9f 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKDJIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKDJIndex.java @@ -23,14 +23,21 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/16

* * @author afon + * @since 2021-05-09 */ - public class StockKDJIndex extends StockIndex { - + /** + * StockKDJIndex + */ public StockKDJIndex() { super(STANDARD_HEIGHT); } + /** + * StockKDJIndex + * + * @param height + */ public StockKDJIndex(int height) { super(height); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKLineVolumeIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKLineVolumeIndex.java index 6f1b76650747a656c46d513b7ead622e46e858a4..968d5d9612dae84d85ed72f5b186ff97751be149 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKLineVolumeIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockKLineVolumeIndex.java @@ -5,14 +5,20 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/6/28

* * @author afon + * @since 2021-05-09 */ - public class StockKLineVolumeIndex extends StockIndex { - + /** + * StockKLineVolumeIndex + */ public StockKLineVolumeIndex() { super(STANDARD_HEIGHT); } + /** + * StockKLineVolumeIndex + * @param height + */ public StockKLineVolumeIndex(int height) { super(height); diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockMACDIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockMACDIndex.java index cc96563f9f9a4591e1f745577ff33ad55bf4ea60..87c2cc8bbcfce311b9de89f21bb97b6c52acf6c0 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockMACDIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockMACDIndex.java @@ -23,14 +23,21 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/16

* * @author afon + * @since 2021-05-09 */ - public class StockMACDIndex extends StockIndex { - + /** + * StockMACDIndex + */ public StockMACDIndex() { super(STANDARD_HEIGHT); } + /** + * StockMACDIndex + * + * @param height + */ public StockMACDIndex(int height) { super(height); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockRSIIndex.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockRSIIndex.java index be2da5cee8bc667cdfbc466c7f87fe1acd1d4da3..ebe5c42a576e69583e4f3a3daef1fcb0fd2e14b0 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockRSIIndex.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/entry/StockRSIIndex.java @@ -23,14 +23,21 @@ package com.wordplat.ikvstockchart.entry; *

Date: 2017/3/16

* * @author afon + * @since 2021-05-09 */ - public class StockRSIIndex extends StockIndex { - + /** + * StockRSIIndex + */ public StockRSIIndex() { super(STANDARD_HEIGHT); } + /** + * StockRSIIndex + * + * @param height + */ public StockRSIIndex(int height) { super(height); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/IMarkerView.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/IMarkerView.java index cbe7c07459779df73cab2e3d3963af32c833e7d6..61bca721c63639d587076ad51b783a4031b666de 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/IMarkerView.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/IMarkerView.java @@ -18,32 +18,33 @@ package com.wordplat.ikvstockchart.marker; -import android.graphics.Canvas; -import android.graphics.RectF; import com.wordplat.ikvstockchart.render.AbstractRender; +import ohos.agp.render.Canvas; +import ohos.agp.utils.RectFloat; + /** *

IMarkerView

*

Date: 2017/3/23

* * @author afon + * @since 2021-05-09 */ - public interface IMarkerView { /** * 初始化 MarkerView * * @param contentRect 视图区域 - * @param render render + * @param render render */ - void onInitMarkerView(RectF contentRect, AbstractRender render); + void onInitMarkerView(RectFloat contentRect, AbstractRender render); /** * onDrawMarkerView * - * @param canvas canvas + * @param canvas canvas * @param highlightPointX 高亮中心坐标 x * @param highlightPointY 高亮中心坐标 y */ diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/XAxisTextMarkerView.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/XAxisTextMarkerView.java index 411e59b13688b819b40ac1f6ee5c4437c55177a2..51472b9284dcdb098c8c05ba4046b82df53c97b0 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/XAxisTextMarkerView.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/XAxisTextMarkerView.java @@ -18,64 +18,75 @@ package com.wordplat.ikvstockchart.marker; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Region; import com.wordplat.ikvstockchart.align.XMarkerAlign; import com.wordplat.ikvstockchart.entry.Entry; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; /** *

YAxisTextMarkerView

*

Date: 2017/3/23

* * @author afon + * @since 2021-05-09 */ - public class XAxisTextMarkerView implements IMarkerView { private static final String TAG = "XAxisTextMarkerView"; private Paint markerTextPaint; private Paint markerBorderPaint; - private final RectF contentRect = new RectF(); + private final RectFloat contentRect = new RectFloat(); private AbstractRender render; - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0,0,0,0,0); private final int height; - private final RectF markerInsets = new RectF(0, 0, 0, 0); + private final RectFloat markerInsets = new RectFloat(0, 0, 0, 0); private float inset = 0; private XMarkerAlign xMarkerAlign; + /** + * XAxisTextMarkerView + * @param height + */ public XAxisTextMarkerView(int height) { this.height = height; } @Override - public void onInitMarkerView(RectF contentRect, AbstractRender render) { - this.contentRect.set(contentRect); + public void onInitMarkerView(RectFloat contentRect, AbstractRender render) { + this.contentRect.left = contentRect.left; + this.contentRect.right = contentRect.right; + this.contentRect.top = contentRect.top; + this.contentRect.bottom = contentRect.bottom; this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (markerTextPaint == null) { - markerTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - markerTextPaint.setTextAlign(Paint.Align.CENTER); + markerTextPaint = new Paint(); + markerTextPaint.setAntiAlias(true); + markerTextPaint.setTextAlign(TextAlignment.CENTER); } - markerTextPaint.setColor(sizeColor.getMarkerTextColor()); - markerTextPaint.setTextSize(sizeColor.getMarkerTextSize()); - markerTextPaint.getFontMetrics(fontMetrics); + markerTextPaint.setColor(new Color(sizeColor.getMarkerTextColor())); + markerTextPaint.setTextSize(StringUtils.floatToInt(sizeColor.getMarkerTextSize())); + markerTextPaint.getFontMetrics(); if (markerBorderPaint == null) { - markerBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - markerBorderPaint.setStyle(Paint.Style.STROKE); + markerBorderPaint = new Paint(); + markerBorderPaint.setAntiAlias(true); + markerBorderPaint.setStyle(Paint.Style.STROKE_STYLE); } markerBorderPaint.setStrokeWidth(sizeColor.getMarkerBorderSize()); - markerBorderPaint.setColor(sizeColor.getMarkerBorderColor()); + markerBorderPaint.setColor(new Color(sizeColor.getMarkerBorderColor())); inset = markerBorderPaint.getStrokeWidth() / 2; xMarkerAlign = sizeColor.getXMarkerAlign(); @@ -105,7 +116,7 @@ public class XAxisTextMarkerView implements IMarkerView { } else if (xMarkerAlign == XMarkerAlign.BOTTOM) { markerInsets.top = contentRect.bottom - height + inset; - } else if (highlightPointY < contentRect.top + contentRect.height() / 3) { + } else if (highlightPointY < contentRect.top + contentRect.getHeight() / 3) { markerInsets.top = contentRect.bottom - height + inset; } else { @@ -115,14 +126,16 @@ public class XAxisTextMarkerView implements IMarkerView { markerInsets.right = markerInsets.left + width - inset * 2; markerInsets.bottom = markerInsets.top + height - inset * 2; - canvas.drawText(highlightEntry.getXLabel(), - markerInsets.left + markerInsets.width() / 2, - (markerInsets.top + markerInsets.bottom - fontMetrics.top - fontMetrics.bottom) / 2, - markerTextPaint); + canvas.drawText(markerTextPaint, + highlightEntry.getXLabel(), + markerInsets.left + markerInsets.getWidth() / 2, + (markerInsets.top + markerInsets.bottom - fontMetrics.top - fontMetrics.bottom) / 2); canvas.drawRect(markerInsets, markerBorderPaint); - - canvas.clipRect(markerInsets, Region.Op.XOR); + /** + * todo 交集方法暂无 + */ + canvas.clipRect(markerInsets, Canvas.ClipOp.DIFFERENCE); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/YAxisTextMarkerView.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/YAxisTextMarkerView.java index ff543cfa3a7f7608d88d64025d7c580ccce6c75e..51b630a66b319b9ad92f934bc881e799c2dfe8c9 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/YAxisTextMarkerView.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/marker/YAxisTextMarkerView.java @@ -18,14 +18,16 @@ package com.wordplat.ikvstockchart.marker; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Region; import com.wordplat.ikvstockchart.align.YMarkerAlign; import com.wordplat.ikvstockchart.entry.SizeColor; import com.wordplat.ikvstockchart.render.AbstractRender; +import com.wordplat.ikvstockchart.utils.StringUtils; +import ohos.agp.render.Canvas; +import ohos.agp.render.Paint; +import ohos.agp.utils.Color; +import ohos.agp.utils.RectFloat; +import ohos.agp.utils.TextAlignment; import java.text.DecimalFormat; @@ -34,22 +36,22 @@ import java.text.DecimalFormat; *

Date: 2017/3/23

* * @author afon + * @since 2021-05-09 */ - public class YAxisTextMarkerView implements IMarkerView { private static final String TAG = "YAxisTextMarkerView"; private Paint markerTextPaint; private Paint markerBorderPaint; - private final RectF contentRect = new RectF(); + private final RectFloat contentRect = new RectFloat(); private AbstractRender render; - private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(); + private final Paint.FontMetrics fontMetrics = new Paint.FontMetrics(0, 0, 0, 0, 0); private final DecimalFormat decimalFormatter = new DecimalFormat("0.00"); private final float[] pointCache = new float[2]; private final int height; - private final RectF markerInsets = new RectF(0, 0, 0, 0); + private final RectFloat markerInsets = new RectFloat(0, 0, 0, 0); private float inset = 0; private YMarkerAlign yMarkerAlign; @@ -58,27 +60,32 @@ public class YAxisTextMarkerView implements IMarkerView { } @Override - public void onInitMarkerView(RectF contentRect, AbstractRender render) { - this.contentRect.set(contentRect); + public void onInitMarkerView(RectFloat contentRect, AbstractRender render) { + this.contentRect.left = contentRect.left; + this.contentRect.right = contentRect.right; + this.contentRect.top = contentRect.top; + this.contentRect.bottom = contentRect.bottom; this.render = render; final SizeColor sizeColor = render.getSizeColor(); if (markerTextPaint == null) { - markerTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - markerTextPaint.setTextAlign(Paint.Align.CENTER); + markerTextPaint = new Paint(); + markerTextPaint.setAntiAlias(true); + markerTextPaint.setTextAlign(TextAlignment.CENTER); } - markerTextPaint.setColor(sizeColor.getMarkerTextColor()); - markerTextPaint.setTextSize(sizeColor.getMarkerTextSize()); - markerTextPaint.getFontMetrics(fontMetrics); + markerTextPaint.setColor(new Color(sizeColor.getMarkerTextColor())); + markerTextPaint.setTextSize(StringUtils.floatToInt(sizeColor.getMarkerTextSize())); + markerTextPaint.getFontMetrics(); if (markerBorderPaint == null) { - markerBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - markerBorderPaint.setStyle(Paint.Style.STROKE); + markerBorderPaint = new Paint(); + markerBorderPaint.setAntiAlias(true); + markerBorderPaint.setStyle(Paint.Style.STROKE_STYLE); } markerBorderPaint.setStrokeWidth(sizeColor.getMarkerBorderSize()); - markerBorderPaint.setColor(sizeColor.getMarkerBorderColor()); + markerBorderPaint.setColor(new Color(sizeColor.getMarkerBorderColor())); inset = markerBorderPaint.getStrokeWidth() / 2; yMarkerAlign = sizeColor.getYMarkerAlign(); @@ -109,7 +116,7 @@ public class YAxisTextMarkerView implements IMarkerView { } else if (yMarkerAlign == YMarkerAlign.RIGHT) { markerInsets.left = contentRect.right - width + inset; - } else if (highlightPointX < contentRect.left + contentRect.width() / 3) { + } else if (highlightPointX < contentRect.left + contentRect.getWidth() / 3) { markerInsets.left = contentRect.right - width + inset; } else { @@ -120,14 +127,16 @@ public class YAxisTextMarkerView implements IMarkerView { markerInsets.right = markerInsets.left + width - inset * 2; markerInsets.bottom = markerInsets.top + height - inset * 2; - canvas.drawText(value, - markerInsets.left + markerInsets.width() / 2, - (markerInsets.top + markerInsets.bottom - fontMetrics.top - fontMetrics.bottom) / 2, - markerTextPaint); + canvas.drawText(markerTextPaint, + value, + markerInsets.left + markerInsets.getWidth() / 2, + (markerInsets.top + markerInsets.bottom - fontMetrics.top - fontMetrics.bottom) / 2); canvas.drawRect(markerInsets, markerBorderPaint); - - canvas.clipRect(markerInsets, Region.Op.XOR); + /** + * todo 交集方法暂无 + */ + canvas.clipRect(markerInsets, Canvas.ClipOp.DIFFERENCE); } } } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/AbstractRender.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/AbstractRender.java index 878392223f0ce90f4c9214fe9df3731cceb637bb..29d1dc37dd528b2dfdd1ae7f45d5843f62db3ff8 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/AbstractRender.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/AbstractRender.java @@ -18,56 +18,119 @@ package com.wordplat.ikvstockchart.render; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.RectF; -import android.util.Log; - import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.SizeColor; +import ohos.agp.render.Canvas; +import ohos.agp.utils.Matrix; +import ohos.agp.utils.RectFloat; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + /** *

AbstractRender

*

Date: 2017/3/3

* * @author afon + * @since 2021-05-09 */ public abstract class AbstractRender { + static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "AbstractRender"); + private static final String TAG = "AbstractRender"; private static final boolean DEBUG = false; + public static final int MSCALE_X = 0; - protected EntrySet entrySet; // entry 值 + public static final int MSKEW_X = 1; - protected SizeColor sizeColor = new SizeColor(); // 画笔大小和颜色配置 + public static final int MTRANS_X = 2; + public static final int MSKEW_Y = 3; + public static final int MSCALE_Y = 4; + public static final int MTRANS_Y = 5; + public static final int MPERSP_0 = 6; + public static final int MPERSP_1 = 7; + public static final int MPERSP_2 = 8; - protected final RectF viewRect = new RectF(); // 整个的视图区域大小 - protected boolean viewRectChange = false; // 视图区域是否发生变化 - - private final Matrix matrixValue = new Matrix(); // 把值映射到屏幕像素的矩阵 - private final Matrix matrixTouch = new Matrix(); // 缩放和平移矩阵 - private final Matrix matrixOffset = new Matrix(); // 偏移矩阵 - private final Matrix matrixSimple = new Matrix(); // 单一矩阵 - private final Matrix matrixInvert = new Matrix(); // 用于缓存反转矩阵 + /** + * entry 值 + */ + protected EntrySet entrySet; + /** + * 画笔大小和颜色配置 + */ + protected SizeColor sizeColor = new SizeColor(); + /** + * 整个的视图区域大小 + */ + protected final RectFloat viewRect = new RectFloat(); // + /** + * 视图区域是否发生变化 + */ + protected boolean viewRectChange = false; // + /** + * 把值映射到屏幕像素的矩阵 + */ + private final Matrix matrixValue = new Matrix(); // + /** + * 缩放和平移矩阵 + */ + private final Matrix matrixTouch = new Matrix(); // + /** + * 偏移矩阵 + */ + private final Matrix matrixOffset = new Matrix(); // + /** + * 单一矩阵 + */ + private final Matrix matrixSimple = new Matrix(); // + /** + * 用于缓存反转矩阵 + */ + private final Matrix matrixInvert = new Matrix(); // private final float[] touchPts = new float[2]; - private final float[] touchValues = new float[9]; // 存储缩放和平移信息 - private final float[] extremumY = new float[2]; // 当前显示区域内的 Y 轴范围 - private float extremumYScale = 1.3f; // Y 轴极值缩放因子 - private float extremumYDelta = 0; // Y 轴极值增量 + /** + * 存储缩放和平移信息 + */ + private final float[] touchValues = new float[9]; // + /** + * 当前显示区域内的 Y 轴范围 + */ + private final float[] extremumY = new float[2]; // + /** + * Y 轴极值缩放因子 + */ + private float extremumYScale = 1.3f; // + /** + * Y 轴极值增量 + */ + private float extremumYDelta = 0; // private boolean highlight = false; private final float[] highlightPoint = new float[2]; + /** + * 最小滚动量 + */ + private float minScrollOffset = 0; // + /** + * 最大滚动量 + */ + private float maxScrollOffset = 0; // + /** + * 上一次的最大滚动量 + */ + private float lastMaxScrollOffset = 0; // + /** + * 超出边界的滚动量 + */ + private float overScrollOffset = 0; // - private float minScrollOffset = 0; // 最小滚动量 - private float maxScrollOffset = 0; // 最大滚动量 - private float lastMaxScrollOffset = 0; // 上一次的最大滚动量 - private float overScrollOffset = 0; // 超出边界的滚动量 /** * 根据给出的 view 坐标信息,进行视图上的一些设置 */ - public abstract void onViewRect(RectF viewRect); + public abstract void onViewRect(RectFloat viewRect); /** * 放大 @@ -121,19 +184,22 @@ public abstract class AbstractRender { /** * 获取整个的视图区域大小 */ - public RectF getViewRect() { + public RectFloat getViewRect() { return viewRect; } /** * 设置整个的视图区域大小 */ - public void setViewRect(RectF viewRect) { - if (this.viewRect.height() > 0 && !this.viewRect.equals(viewRect)) { + public void setViewRect(RectFloat viewRect) { + if (this.viewRect.getHeight() > 0 && !this.viewRect.equals(viewRect)) { viewRectChange = true; } - this.viewRect.set(viewRect); + this.viewRect.left = viewRect.left; + this.viewRect.right = viewRect.right; + this.viewRect.top = viewRect.top; + this.viewRect.bottom = viewRect.bottom; } /** @@ -192,15 +258,14 @@ public abstract class AbstractRender { * 是否可以滚动 */ public boolean canScroll(float dx) { - final float offset = touchValues[Matrix.MTRANS_X] - dx; + final float offset = touchValues[MTRANS_X] - dx; - if (offset < -maxScrollOffset && touchValues[Matrix.MTRANS_X] <= -maxScrollOffset) { + if (offset < -maxScrollOffset && touchValues[MTRANS_X] <= -maxScrollOffset) { return false; } - if (offset > minScrollOffset && touchValues[Matrix.MTRANS_X] >= minScrollOffset) { + if (offset > minScrollOffset && touchValues[MTRANS_X] >= minScrollOffset) { return false; } - return true; } @@ -240,7 +305,7 @@ public abstract class AbstractRender { * 获取当前滚动量 */ public float getCurrentTransX() { - return touchValues[Matrix.MTRANS_X]; + return touchValues[MTRANS_X]; } /** @@ -249,11 +314,11 @@ public abstract class AbstractRender { * @param dx 变化量 */ public void updateCurrentTransX(float dx) { - matrixTouch.getValues(touchValues); + matrixTouch.getData(); - touchValues[Matrix.MTRANS_X] += -dx; + touchValues[MTRANS_X] += -dx; - matrixTouch.setValues(touchValues); + matrixTouch.setMatrix(new Matrix(touchValues)); } /** @@ -262,11 +327,11 @@ public abstract class AbstractRender { * @param transX 当前滚动位置。此值为正时将被程序视为负,因为这里的滚动量用负数表示。 */ public void setCurrentTransX(float transX) { - matrixTouch.getValues(touchValues); + matrixTouch.getData(); - touchValues[Matrix.MTRANS_X] = transX > 0 ? -transX : transX; + touchValues[MTRANS_X] = transX > 0 ? -transX : transX; - matrixTouch.setValues(touchValues); + matrixTouch.setMatrix(new Matrix(touchValues)); } /** @@ -281,9 +346,9 @@ public abstract class AbstractRender { matrixValue.mapPoints(touchPts); matrixTouch.mapPoints(touchPts); - matrixTouch.getValues(touchValues); + matrixTouch.getData(); - return touchValues[Matrix.MTRANS_X] + touchPts[0]; + return touchValues[MTRANS_X] + touchPts[0]; } /** @@ -292,30 +357,30 @@ public abstract class AbstractRender { * @param dx 变化量 */ public void scroll(float dx) { - matrixTouch.getValues(touchValues); + matrixTouch.getData(); - touchValues[Matrix.MTRANS_X] += -dx; + touchValues[MTRANS_X] += -dx; overScrollOffset = 0; - if (touchValues[Matrix.MTRANS_X] < -maxScrollOffset) { - touchValues[Matrix.MTRANS_X] = -maxScrollOffset; + if (touchValues[MTRANS_X] < -maxScrollOffset) { + touchValues[MTRANS_X] = -maxScrollOffset; } - if (touchValues[Matrix.MTRANS_X] > minScrollOffset) { - touchValues[Matrix.MTRANS_X] = minScrollOffset; + if (touchValues[MTRANS_X] > minScrollOffset) { + touchValues[MTRANS_X] = minScrollOffset; } - matrixTouch.setValues(touchValues); + matrixTouch.setMatrix(new Matrix(touchValues)); } /** * 缩放 * - * @param contentRect 当前显示区域 + * @param contentRect 当前显示区域 * @param visibleCount 当前显示区域的 X 轴方向上需要显示多少个 entry 值 - * @param x 在点(x, y)上缩放 - * @param y 在点(x, y)上缩放。由于 K 线图只会进行水平滚动,因此 y 值被忽略 + * @param x 在点(x, y)上缩放 + * @param y 在点(x, y)上缩放。由于 K 线图只会进行水平滚动,因此 y 值被忽略 */ - public void zoom(RectF contentRect, float visibleCount, float x, float y) { + public void zoom(RectFloat contentRect, float visibleCount, float x, float y) { if (x < contentRect.left) { x = contentRect.left; } @@ -323,10 +388,10 @@ public abstract class AbstractRender { x = contentRect.right; } - matrixTouch.getValues(touchValues); + matrixTouch.getData(); final int minVisibleIndex; - final int toMinVisibleIndex = (int) (visibleCount * (x - contentRect.left) / contentRect.width()); + final int toMinVisibleIndex = (int) (visibleCount * (x - contentRect.left) / contentRect.getWidth()); touchPts[0] = x; touchPts[1] = 0; @@ -338,17 +403,17 @@ public abstract class AbstractRender { minVisibleIndex = (int) Math.abs(touchPts[0] - toMinVisibleIndex); } - touchValues[Matrix.MSCALE_X] = entrySet.getEntryList().size() / visibleCount; + touchValues[MSCALE_X] = entrySet.getEntryList().size() / visibleCount; - computeScrollRange(contentRect.width(), touchValues[Matrix.MSCALE_X]); + computeScrollRange(contentRect.getWidth(), touchValues[MSCALE_X]); - touchValues[Matrix.MTRANS_X] = getTransX(visibleCount, minVisibleIndex); + touchValues[MTRANS_X] = getTransX(visibleCount, minVisibleIndex); - matrixTouch.setValues(touchValues); + matrixTouch.setMatrix(new Matrix(touchValues)); if (DEBUG) { - Log.i(TAG, "##d zoom: touchValues[Matrix.MSCALE_X] = " + touchValues[Matrix.MSCALE_X] - + ", touchValues[Matrix.MTRANS_X] = " + touchValues[Matrix.MTRANS_X] + HiLog.info(LOG_LABEL, "##d zoom: touchValues[Matrix.MSCALE_X] = " + touchValues[MSCALE_X] + + ", touchValues[Matrix.MTRANS_X] = " + touchValues[MTRANS_X] + ", visibleCount = " + visibleCount + ", minVisibleIndex = " + minVisibleIndex); } @@ -369,11 +434,11 @@ public abstract class AbstractRender { * 按给定矩阵将 entry 的值映射到屏幕像素上 * * @param matrix 矩阵 - * @param pts 浮点数序列 [x0, y0, x1, y1, ...] + * @param pts 浮点数序列 [x0, y0, x1, y1, ...] */ public void mapPoints(Matrix matrix, float[] pts) { if (matrix != null) { - matrixSimple.set(matrix); + matrixSimple.setMatrix(matrix); } if (matrixSimple.isIdentity()) { @@ -405,11 +470,11 @@ public abstract class AbstractRender { * 将基于屏幕像素的坐标按给定矩阵反转到值 * * @param matrix 矩阵 - * @param pts 浮点数序列 [x0, y0, x1, y1, ...] + * @param pts 浮点数序列 [x0, y0, x1, y1, ...] */ public void invertMapPoints(Matrix matrix, float[] pts) { if (matrix != null) { - matrixSimple.set(matrix); + matrixSimple.setMatrix(matrix); } if (matrixSimple.isIdentity()) { @@ -427,40 +492,40 @@ public abstract class AbstractRender { * 值矩阵运算 * * @param matrix 矩阵 matrix - * @param rect 视图 rect - * @param minY 较小的 Y 值 + * @param rect 视图 rect + * @param minY 较小的 Y 值 * @param deltaY Y 轴范围 */ - protected void postMatrixValue(Matrix matrix, RectF rect, float minY, float deltaY) { + protected void postMatrixValue(Matrix matrix, RectFloat rect, float minY, float deltaY) { final float maxY = minY + deltaY; if (maxY > 0) { if (minY >= 0) { matrix.reset(); matrix.postTranslate(0, -minY); - matrix.postScale(1, -(rect.height() / deltaY)); + matrix.postScale(1, -(rect.getHeight() / deltaY)); matrix.postTranslate(0, rect.bottom); } else { matrix.reset(); - matrix.postScale(1, -(rect.height() / deltaY)); - matrix.postTranslate(0, rect.top + maxY / deltaY * rect.height()); + matrix.postScale(1, -(rect.getHeight() / deltaY)); + matrix.postTranslate(0, rect.top + maxY / deltaY * rect.getHeight()); } } else { matrix.reset(); matrix.postTranslate(0, -maxY); - matrix.postScale(1, -(rect.height() / deltaY)); + matrix.postScale(1, -(rect.getHeight() / deltaY)); matrix.postTranslate(0, rect.top); } - matrixSimple.set(matrix); + matrixSimple.setMatrix(matrix); } /** * 值矩阵运算 * - * @param width 当前显示区域的宽 + * @param width 当前显示区域的宽 * @param height 当前显示区域的高 - * @param minY 显示区域内可见的 entry 的最小值 + * @param minY 显示区域内可见的 entry 的最小值 * @param deltaY 显示区域内可见的 entry 最大值与最小值之差 */ protected void postMatrixValue(float width, float height, float minY, float deltaY) { @@ -478,7 +543,7 @@ public abstract class AbstractRender { /** * 手势滑动缩放矩阵运算 * - * @param width 当前显示区域的宽 + * @param width 当前显示区域的宽 * @param visibleCount 当前显示区域的 X 轴方向上需要显示多少个 entry 值 */ protected void postMatrixTouch(float width, float visibleCount) { @@ -493,38 +558,48 @@ public abstract class AbstractRender { computeScrollRange(width, scaleX); if (DEBUG) { - Log.i(TAG, "##d postMatrixTouch: currentOffset = " + touchValues[Matrix.MTRANS_X] + HiLog.info(LOG_LABEL, "##d postMatrixTouch: currentOffset = " + touchValues[MTRANS_X] + ", maxScrollOffset = " + maxScrollOffset + ", lastMaxScrollOffset = " + lastMaxScrollOffset + ", overScrollOffset = " + overScrollOffset); } - // 数据不满一屏,不需要滚动定位 + /** + * 数据不满一屏,不需要滚动定位 + */ if (scaleX > 1) { - if (touchValues[Matrix.MTRANS_X] > 0) { - // 左滑加载完成之后定位到之前滚动的位置 - matrixTouch.postTranslate(touchValues[Matrix.MTRANS_X] - (maxScrollOffset - lastMaxScrollOffset), 0); + if (touchValues[MTRANS_X] > 0) { + /** + * 左滑加载完成之后定位到之前滚动的位置 + */ + matrixTouch.postTranslate(touchValues[MTRANS_X] - (maxScrollOffset - lastMaxScrollOffset), 0); - } else if (touchValues[Matrix.MTRANS_X] < 0) { + } else if (touchValues[MTRANS_X] < 0) { if (overScrollOffset != 0 && !viewRectChange) { - // 右滑加载完成之后定位到之前滚动的位置 - matrixTouch.postTranslate(touchValues[Matrix.MTRANS_X], 0); + /** + * 右滑加载完成之后定位到之前滚动的位置 + */ + matrixTouch.postTranslate(touchValues[MTRANS_X], 0); } else { // TODO 左滑、右滑加载中时转动屏幕方向,定位仍然有 BUG,我将花更多时间找到好的办法解决 - // 转动屏幕方向导致矩形变化,定位到之前相同比例的滚动位置 - touchValues[Matrix.MTRANS_X] = touchValues[Matrix.MTRANS_X] / lastMaxScrollOffset * maxScrollOffset; + /** + * 转动屏幕方向导致矩形变化,定位到之前相同比例的滚动位置 + */ + touchValues[MTRANS_X] = touchValues[MTRANS_X] / lastMaxScrollOffset * maxScrollOffset; if (DEBUG) { - Log.i(TAG, "##d postMatrixTouch: transX = " + touchValues[Matrix.MTRANS_X] + ", viewRectChange = " + viewRectChange); + HiLog.info(LOG_LABEL, "##d postMatrixTouch: transX = " + touchValues[MTRANS_X] + ", viewRectChange = " + viewRectChange); } - matrixTouch.postTranslate(touchValues[Matrix.MTRANS_X], 0); + matrixTouch.postTranslate(touchValues[MTRANS_X], 0); viewRectChange = false; } } else { - // 通常首次加载时定位到最末尾 + /** + * 通常首次加载时定位到最末尾 + */ setCurrentTransX(-maxScrollOffset); } } @@ -545,7 +620,7 @@ public abstract class AbstractRender { * 获取给定的 entryIndex 对应的滚动偏移量。在调用 {@link #computeScrollRange} 之后才能调用此方法 * * @param visibleCount 当前显示区域的 X 轴方向上需要显示多少个 entry 值 - * @param entryIndex entry 索引 + * @param entryIndex entry 索引 */ protected float getTransX(float visibleCount, int entryIndex) { final int entrySetSize = entrySet.getEntryList().size(); @@ -561,7 +636,7 @@ public abstract class AbstractRender { /** * 计算当前缩放下,X 轴方向的最小滚动值和最大滚动值 * - * @param width 当前显示区域的宽 + * @param width 当前显示区域的宽 * @param scaleX X 轴方向的缩放 */ protected void computeScrollRange(float width, float scaleX) { diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/KLineRender.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/KLineRender.java index 45cf84b916b97c162b9a9990ea898c0d207f994d..0936bad1e96f0e6421fa5280753d7050299ab6fe 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/KLineRender.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/KLineRender.java @@ -1,13 +1,13 @@ /* * Copyright (C) 2017 WordPlat Open Source Project * - * https://wordplat.com/InteractiveKLineView/ + * https:wordplat.com/InteractiveKLineView/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http:www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -18,22 +18,18 @@ package com.wordplat.ikvstockchart.render; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.RectF; -import android.view.animation.LinearInterpolator; - -import com.wordplat.ikvstockchart.drawing.CandleDrawing; -import com.wordplat.ikvstockchart.drawing.EmptyDataDrawing; -import com.wordplat.ikvstockchart.drawing.HighlightDrawing; -import com.wordplat.ikvstockchart.drawing.IDrawing; -import com.wordplat.ikvstockchart.drawing.KLineGridAxisDrawing; -import com.wordplat.ikvstockchart.drawing.MADrawing; +import com.wordplat.ikvstockchart.drawing.*; import com.wordplat.ikvstockchart.entry.EntrySet; import com.wordplat.ikvstockchart.entry.StockIndex; import com.wordplat.ikvstockchart.marker.IMarkerView; +import com.wordplat.ikvstockchart.utils.StringUtils; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorValue; +import ohos.agp.render.Canvas; +import ohos.agp.utils.RectFloat; +import ohos.app.Context; +import ohos.global.configuration.Configuration; import java.util.ArrayList; import java.util.List; @@ -43,8 +39,8 @@ import java.util.List; *

Date: 2017/3/3

* * @author afon + * @since 2021-05-09 */ - public class KLineRender extends AbstractRender { private static final String TAG = "KLineRender"; @@ -55,7 +51,10 @@ public class KLineRender extends AbstractRender { private static final float LANDSCAPE_PORTRAIT_FACTOR = 1.8235294f; private final Context context; - private final RectF kLineRect = new RectF(); // K 线图显示区域 + /** + * K 线图显示区域 + */ + private final RectFloat kLineRect = new RectFloat(); private final float[] extremumY = new float[2]; private final float[] contentPts = new float[2]; @@ -95,7 +94,7 @@ public class KLineRender extends AbstractRender { /** * 缩放动画 */ - private final ValueAnimator zoomAnimator = new ValueAnimator(); + private final AnimatorValue zoomAnimator = new AnimatorValue(); private float zoomPivotX; private float zoomPivotY; @@ -108,9 +107,16 @@ public class KLineRender extends AbstractRender { private final MADrawing maDrawing = new MADrawing(); private final EmptyDataDrawing emptyDataDrawing = new EmptyDataDrawing(); private final HighlightDrawing highlightDrawing = new HighlightDrawing(); + /** + * 股票指标列表 + */ + private final List stockIndexList = new ArrayList<>(); - private final List stockIndexList = new ArrayList<>(); // 股票指标列表 - + /** + * KLineRender + * + * @param context + */ public KLineRender(Context context) { this.context = context; @@ -121,33 +127,54 @@ public class KLineRender extends AbstractRender { kLineDrawingList.add(highlightDrawing); zoomAnimator.setDuration(ZOOM_DURATION); - zoomAnimator.setInterpolator(new LinearInterpolator()); - zoomAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int count = (int) animation.getAnimatedValue(); + zoomAnimator.setCurveType(Animator.CurveType.LINEAR); + zoomAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() { + @Override + public void onUpdate(AnimatorValue animatorValue, float v) { + int count = StringUtils.floatToInt(v); zoom(kLineRect, count, zoomPivotX, zoomPivotY); } }); } + /** + * addDrawing + * + * @param drawing + */ public void addDrawing(IDrawing drawing) { kLineDrawingList.add(drawing); } + /** + * clearDrawing + */ public void clearDrawing() { kLineDrawingList.clear(); } + /** + * addStockIndex + * + * @param stockIndex + */ public void addStockIndex(StockIndex stockIndex) { stockIndexList.add(stockIndex); } + /** + * removeStockIndex + * + * @param stockIndex + */ public void removeStockIndex(StockIndex stockIndex) { stockIndexList.remove(stockIndex); } + /** + * clearStockIndex + */ public void clearStockIndex() { stockIndexList.clear(); } @@ -160,10 +187,15 @@ public class KLineRender extends AbstractRender { this.zoomTimes = zoomTimes; } - public RectF getKLineRect() { + public RectFloat getKLineRect() { return kLineRect; } + /** + * addMarkerView + * + * @param markerView + */ public void addMarkerView(IMarkerView markerView) { highlightDrawing.addMarkerView(markerView); } @@ -174,16 +206,16 @@ public class KLineRender extends AbstractRender { computeVisibleCount(); - postMatrixTouch(kLineRect.width(), currentVisibleCount); + postMatrixTouch(kLineRect.getWidth(), currentVisibleCount); computeExtremumValue(extremumY, entrySet.getMinY(), entrySet.getDeltaY()); - postMatrixValue(kLineRect.width(), kLineRect.height(), extremumY[0], extremumY[1]); + postMatrixValue(kLineRect.getWidth(), kLineRect.getHeight(), extremumY[0], extremumY[1]); postMatrixOffset(kLineRect.left, kLineRect.top); } @Override - public void onViewRect(RectF viewRect) { + public void onViewRect(RectFloat viewRect) { final float candleBottom = viewRect.bottom - sizeColor.getXLabelViewHeight(); final int remainHeight = (int) (candleBottom - viewRect.top); @@ -197,7 +229,7 @@ public class KLineRender extends AbstractRender { } } - kLineRect.set(viewRect.left, viewRect.top, viewRect.right, candleBottom - calculateHeight); + kLineRect.modify(viewRect.left, viewRect.top, viewRect.right, candleBottom - calculateHeight); initDrawingList(kLineRect, kLineDrawingList); @@ -206,7 +238,8 @@ public class KLineRender extends AbstractRender { if (stockIndex.isEnable()) { calculateHeight += stockIndex.getHeight(); - float top = kLineRect.bottom + sizeColor.getXLabelViewHeight() + calculateHeight - stockIndex.getHeight(); + float top = kLineRect.bottom + sizeColor.getXLabelViewHeight() + + calculateHeight - stockIndex.getHeight(); float bottom = kLineRect.bottom + sizeColor.getXLabelViewHeight() + calculateHeight; stockIndex.setRect( @@ -229,7 +262,7 @@ public class KLineRender extends AbstractRender { @Override public void zoomIn(float x, float y) { if (entrySet.getEntryList().size() == 0) { - return ; + return; } final int visibleCount = getCurrentVisibleCount(++zoomTimes); @@ -251,7 +284,7 @@ public class KLineRender extends AbstractRender { @Override public void zoomOut(float x, float y) { if (entrySet.getEntryList().size() == 0) { - return ; + return; } final int visibleCount = getCurrentVisibleCount(--zoomTimes); @@ -266,6 +299,9 @@ public class KLineRender extends AbstractRender { @Override public void render(Canvas canvas) { + if (entrySet == null) { + return; + } final int count = entrySet.getEntryList().size(); computeVisibleIndex(); @@ -297,24 +333,14 @@ public class KLineRender extends AbstractRender { } } - private void zoomAnimate(int visibleCount, float pivotX, float pivotY) { - zoomAnimator.setIntValues(currentVisibleCount, visibleCount); - zoomPivotX = pivotX; - zoomPivotY = pivotY; - - currentVisibleCount = visibleCount; - - zoomAnimator.start(); - } - - private void initDrawingList(RectF rect, List drawingList) { + private void initDrawingList(RectFloat rect, List drawingList) { for (IDrawing drawing : drawingList) { drawing.onInit(rect, this); } } private void renderDrawingList(Canvas canvas, List drawingList, float minY, float maxY) { - for (int i = minVisibleIndex ; i < maxVisibleIndex ; i++) { + for (int i = minVisibleIndex; i < maxVisibleIndex; i++) { for (IDrawing drawing : drawingList) { drawing.computePoint(minVisibleIndex, maxVisibleIndex, i); } @@ -345,22 +371,24 @@ public class KLineRender extends AbstractRender { currentVisibleCount = sizeColor.getPortraitDefaultVisibleCount(); portraitVisibleCountBuffer[zoomOutTimes] = currentVisibleCount; - for (int i = zoomInTimes ; i > 0 ; i--) { + for (int i = zoomInTimes; i > 0; i--) { portraitVisibleCountBuffer[zoomOutTimes - i] = getZoomOutVisibleCount(currentVisibleCount, i); } - for (int i = zoomOutTimes ; i > 0 ; i--) { + for (int i = zoomOutTimes; i > 0; i--) { portraitVisibleCountBuffer[zoomOutTimes + i] = getZoomInVisibleCount(currentVisibleCount, i); } } - // 横屏时应该改变显示的 entry 数量,否则蜡烛图太粗了,不好看 - if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + /** + * 横屏时应该改变显示的 entry 数量,否则蜡烛图太粗了,不好看 + */ + if (context.getResourceManager().getConfiguration().direction == Configuration.DIRECTION_HORIZONTAL) { if (landscapeVisibleCountBuffer.length < maxZoomTimes) { landscapeVisibleCountBuffer = new int[maxZoomTimes]; } - for (int i = 0 ; i <= zoomOutTimes + zoomInTimes ; i++) { + for (int i = 0; i <= zoomOutTimes + zoomInTimes; i++) { landscapeVisibleCountBuffer[i] = (int) (portraitVisibleCountBuffer[i] * LANDSCAPE_PORTRAIT_FACTOR); } @@ -382,17 +410,19 @@ public class KLineRender extends AbstractRender { maxVisibleIndex = entrySet.getEntryList().size(); } - // 计算当前显示区域内 entry 在 Y 轴上的最小值和最大值 + /** + * 计算当前显示区域内 entry 在 Y 轴上的最小值和最大值 + */ entrySet.computeMinMax(minVisibleIndex, maxVisibleIndex, stockIndexList); computeExtremumValue(extremumY, entrySet.getMinY(), entrySet.getDeltaY()); - postMatrixValue(kLineRect.width(), kLineRect.height(), extremumY[0], extremumY[1]); + postMatrixValue(kLineRect.getWidth(), kLineRect.getHeight(), extremumY[0], extremumY[1]); } private int getCurrentVisibleCount(int zoomTimes) { final int index = zoomOutTimes + zoomTimes; if (0 <= index && index <= zoomOutTimes + zoomInTimes) { - if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (context.getResourceManager().getConfiguration().direction == Configuration.DIRECTION_HORIZONTAL) { return landscapeVisibleCountBuffer[index]; } else { return portraitVisibleCountBuffer[index]; diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/TimeLineRender.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/TimeLineRender.java index dcd2cefe7e2d8bb56d5dc51036e1952e17a08dd5..270eec7dd175e19ba2c324c7e0dd49dc99ca1f09 100644 --- a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/TimeLineRender.java +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/render/TimeLineRender.java @@ -18,9 +18,6 @@ package com.wordplat.ikvstockchart.render; -import android.graphics.Canvas; -import android.graphics.RectF; - import com.wordplat.ikvstockchart.drawing.EmptyDataDrawing; import com.wordplat.ikvstockchart.drawing.HighlightDrawing; import com.wordplat.ikvstockchart.entry.EntrySet; @@ -28,6 +25,9 @@ import com.wordplat.ikvstockchart.drawing.IDrawing; import com.wordplat.ikvstockchart.drawing.TimeLineDrawing; import com.wordplat.ikvstockchart.drawing.TimeLineGridAxisDrawing; +import ohos.agp.render.Canvas; +import ohos.agp.utils.RectFloat; + import java.util.ArrayList; import java.util.List; @@ -36,16 +36,21 @@ import java.util.List; *

Date: 2017/3/9

* * @author afon + * @since 2021-05-09 */ - public class TimeLineRender extends AbstractRender { - - private final RectF chartRect = new RectF(); // 分时图显示区域 + /** + * 分时图显示区域 + */ + private final RectFloat chartRect = new RectFloat(); private final float[] extremumY = new float[2]; private final List drawingList = new ArrayList<>(); + /** + * TimeLineRender + */ public TimeLineRender() { drawingList.add(new TimeLineGridAxisDrawing()); drawingList.add(new TimeLineDrawing()); @@ -53,15 +58,28 @@ public class TimeLineRender extends AbstractRender { drawingList.add(new HighlightDrawing()); } + /** + * addDrawing + * + * @param drawing + */ public void addDrawing(IDrawing drawing) { drawingList.add(drawing); } + /** + * clearDrawing + */ public void clearDrawing() { drawingList.clear(); } - public RectF getChartRect() { + /** + * getChartRect + * + * @return RectFloat + */ + public RectFloat getChartRect() { return chartRect; } @@ -69,11 +87,11 @@ public class TimeLineRender extends AbstractRender { public void setEntrySet(EntrySet entrySet) { super.setEntrySet(entrySet); - postMatrixTouch(chartRect.width(), sizeColor.getTimeLineMaxCount()); + postMatrixTouch(chartRect.getWidth(), sizeColor.getTimeLineMaxCount()); entrySet.computeTimeLineMinMax(0, entrySet.getEntryList().size()); computeExtremumValue(extremumY, entrySet.getMinY(), entrySet.getDeltaY()); - postMatrixValue(chartRect.width(), chartRect.height(), extremumY[0], extremumY[1]); + postMatrixValue(chartRect.getWidth(), chartRect.getHeight(), extremumY[0], extremumY[1]); postMatrixOffset(chartRect.left, chartRect.top); scroll(0); @@ -90,8 +108,11 @@ public class TimeLineRender extends AbstractRender { } @Override - public void onViewRect(RectF viewRect) { - chartRect.set(viewRect); + public void onViewRect(RectFloat viewRect) { + chartRect.left = viewRect.left; + chartRect.right = viewRect.right; + chartRect.top = viewRect.top; + chartRect.bottom = viewRect.bottom; for (IDrawing drawing : drawingList) { drawing.onInit(chartRect, this); @@ -113,7 +134,7 @@ public class TimeLineRender extends AbstractRender { final int count = entrySet.getEntryList().size(); final int lastIndex = count - 1; - for (int i = 0 ; i < count ; i++) { + for (int i = 0; i < count; i++) { for (IDrawing drawing : drawingList) { drawing.computePoint(0, lastIndex, i); } diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/ScreenUtils.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/ScreenUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..24ef5c085c12e8a06bab860ef633cd21d98d4ef4 --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/ScreenUtils.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.wordplat.ikvstockchart.utils; + +import ohos.agp.colors.RgbColor; +import ohos.agp.components.AttrHelper; +import ohos.agp.utils.Color; +import ohos.agp.window.service.Display; +import ohos.agp.window.service.DisplayManager; +import ohos.app.Context; + +import java.util.Optional; + +import static ohos.agp.components.AttrHelper.getDensity; + +/** + * ScreenUtils + * + * @since 2021-03-29 + */ +public class ScreenUtils { + + /** + * 获取屏幕的高度Px + * + * @param context + * @return int + */ + public static final int getHeightInPx(Context context) { + final int height = context.getResourceManager().getDeviceCapability().height; + return height; + } + + /** + * 获取屏幕的宽度Px + * + * @param context + * @return int + */ + public static final int getWidthInPx(Context context) { + final int width = context.getResourceManager().getDeviceCapability().width; + return width; + } + + /** + * 获取屏幕的高度Dp + * + * @param context + * @return int + */ + public static final int getHeightInDp(Context context) { + final float height = context.getResourceManager().getDeviceCapability().height; + int heightInDp = pxToFp(context, height); + return heightInDp; + } + + /** + * 获取屏幕的宽度Dp + * + * @param context + * @return int + */ + public static final int getWidthInDp(Context context) { + final float width = context.getResourceManager().getDeviceCapability().width; + int widthInDp = fpToPx(context, width); + return widthInDp; + } + + /** + * fp 转 Px + * + * @param context + * @param fp + * @return int + */ + public static int fpToPx(final Context context, final float fp) { + + return AttrHelper.fp2px(fp, getDensity(context)); + } + + /** + * px 转 Fp + * + * @param context + * @param px + * @return int + */ + public static int pxToFp(final Context context, final float px) { + return Math.round(px * getDensity(context)); + } + + + /** + * 获取当前设备属性 + * + * @param context + * @return Display + */ + public static Display getDeviceAttr(Context context) { + Optional optional = DisplayManager.getInstance().getDefaultDisplay(context); + return optional.get(); + } + + /** + * 屏幕宽 + * + * @param context + * @return int + */ + public static int getDisplayWidth(Context context) { + return getDeviceAttr(context).getRealAttributes().width; + } + + /** + * 屏幕高 + * + * @param context + * @return int + */ + public static int getDisplayHeight(Context context) { + return getDeviceAttr(context).getRealAttributes().height; + } + + /** + * rgb颜色 + * + * @param color 色值 + * @return RgbColor + */ + private RgbColor getRgbColor(Color color) { + return RgbColor.fromArgbInt(color.getValue()); + } +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/StringUtils.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/StringUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f413fcefef1a3f93c5009b05394836ed0d0a889c --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/StringUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.ikvstockchart.utils; + +/** + * StringUtils + * + * @since 2021-04-26 + */ +public class StringUtils { + private StringUtils() { + } + + /** + * float转int + * + * @param floatnum + * @return int + */ + public static int floatToInt(float floatnum) { + return (int) floatnum; + } +} diff --git a/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/TypedAttrUtils.java b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/TypedAttrUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..56dd91149b88eda796e1c99d1ec02b7772c0a9ea --- /dev/null +++ b/ikvStockChart/src/main/java/com/wordplat/ikvstockchart/utils/TypedAttrUtils.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wordplat.ikvstockchart.utils; + + +import ohos.agp.components.Attr; +import ohos.agp.components.AttrSet; +import ohos.agp.utils.Color; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.util.NoSuchElementException; + +/** + * TypedAttrUtils + * + * @since 2021-03-29 + */ +public class TypedAttrUtils { + static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "TypedAttrUtils"); + + private TypedAttrUtils() { + } + + /** + * getIntColor + * + * @param attrs + * @param attrName + * @param defValue + * @return int + */ + public static int getIntColor(AttrSet attrs, String attrName, int defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getColorValue().getValue(); + } + } + + /** + * getColor + * + * @param attrs + * @param attrName + * @param defValue + * @return Color + */ + public static Color getColor(AttrSet attrs, String attrName, Color defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getColorValue(); + } + } + + /** + * getBoolean + * + * @param attrs + * @param attrName + * @param defValue + * @return boolean + */ + public static boolean getBoolean(AttrSet attrs, String attrName, boolean defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getBoolValue(); + } + } + + /** + * getString + * + * @param attrs + * @param attrName + * @param defValue + * @return String + */ + public static String getString(AttrSet attrs, String attrName, String defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getStringValue(); + } + } + + /** + * getFloat + * + * @param attrs + * @param attrName + * @param defValue + * @return float + */ + public static float getFloat(AttrSet attrs, String attrName, float defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getFloatValue(); + } + } + + /** + * getInteger + * + * @param attrs + * @param attrName + * @param defValue + * @return getInteger + */ + public static int getInteger(AttrSet attrs, String attrName, int defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getIntegerValue(); + } + } + + /** + * getDimensionPixelSize + * + * @param attrs + * @param attrName + * @param defValue + * @return int + */ + public static int getDimensionPixelSize(AttrSet attrs, String attrName, int defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + return attr.getIntegerValue(); + } + } + + /** + * getLayoutDimension + * + * @param attrs + * @param attrName + * @param defValue + * @return int + */ + public static int getLayoutDimension(AttrSet attrs, String attrName, int defValue) { + Attr attr = attrNoSuchElement(attrs, attrName); + if (attr == null) { + return defValue; + } else { + HiLog.info(LOG_LABEL, "attr.getDimensionValue() = " + attr.getDimensionValue()); + return attr.getDimensionValue(); + } + } + + private static Attr attrNoSuchElement(AttrSet attrs, String attrName) { + Attr attr = null; + try { + attr = attrs.getAttr(attrName).get(); + } catch (NoSuchElementException e) { + HiLog.info(LOG_LABEL, "Exception = " + e.toString()); + } + return attr; + } +} diff --git a/ikvStockChart/src/main/res/drawable/text_tab.xml b/ikvStockChart/src/main/res/drawable/text_tab.xml deleted file mode 100644 index b2cc0f0be5f8b4b2da41ea5421a161be949a8b53..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/drawable/text_tab.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/ikvStockChart/src/main/res/layout/tab_stockindex.xml b/ikvStockChart/src/main/res/layout/tab_stockindex.xml deleted file mode 100644 index dc885c51636c39f73f25f625473d3cbc3a7a8d4d..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/layout/tab_stockindex.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/ikvStockChart/src/main/res/values/attrs.xml b/ikvStockChart/src/main/res/values/attrs.xml deleted file mode 100644 index 01cfba17c08b2e5217734cfb80c2aa0b586d9cfa..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/values/attrs.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ikvStockChart/src/main/res/values/dimens.xml b/ikvStockChart/src/main/res/values/dimens.xml deleted file mode 100644 index f60edfd62775c2f3a91d73e1d22aa0682616568e..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/values/dimens.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - 100dp - - - 20dp - - - 15dp - - diff --git a/ikvStockChart/src/main/res/values/strings.xml b/ikvStockChart/src/main/res/values/strings.xml deleted file mode 100644 index c0699c984866e96ea9d5b0024ee1dd5a180d55d7..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/values/strings.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - MA(5, 10, 20) - ●MA5 %1$.2f ●MA10 %2$.2f ●MA20 %3$.2f - - MACD(12, 26, 9) - MACD(12, 26, 9) ●DIFF %1$.2f ●DEA %2$.2f ●MACD %3$.2f - - RSI(6, 12, 24) - RSI(6, 12, 24) ●RSI1 %1$.2f ●RSI2 %2$.2f ●RSI3 %3$.2f - - KDJ(9, 3, 3) - KDJ(9, 3, 3) ●K %1$.2f ●D %2$.2f ●J %3$.2f - - BOLL(20, 2) - BOLL(20, 2) ●MID %1$.2f ●UPPER %2$.2f ●LOWER %3$.2f - - ●MA5 %1$.2f ●MA10 %2$.2f - - \ No newline at end of file diff --git a/ikvStockChart/src/main/res/values/styles.xml b/ikvStockChart/src/main/res/values/styles.xml deleted file mode 100644 index 8a47a4c6b63336db7adea2c21b7705194fe8c7cf..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/main/res/values/styles.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ikvStockChart/src/main/resources/base/element/color.json b/ikvStockChart/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..5c0f2aa72536c8f37336d7a72bc937138719ac73 --- /dev/null +++ b/ikvStockChart/src/main/resources/base/element/color.json @@ -0,0 +1,190 @@ +{ + "color": [ + { + "name": "ic_advertise", + "value": "#FFFFFF" + }, + { + "name": "colorCg", + "value": "#d3d7d4" + }, + { + "name": "layer_grid_line", + "value": "#000000" + }, + { + "name": "app_mainclolr", + "value": "#FB6A8F" + },{ + "name": "app_qing2", + "value": "#31C453" + },{ + "name": "color_1", + "value": "#AAA3DB" + },{ + "name": "color_2", + "value": "#86ACE9" + },{ + "name": "color_4", + "value": "#80D8A3" + },{ + "name": "color_5", + "value": "#F1C672" + },{ + "name": "color_6", + "value": "#FDAD8B" + },{ + "name": "color_7", + "value": "#ADBEFF" + },{ + "name": "color_8", + "value": "#94D6FA" + },{ + "name": "color_9", + "value": "#C3B5F6" + },{ + "name": "color_10", + "value": "#99CCFF" + },{ + "name": "color_11", + "value": "#FBA6ED" + },{ + "name": "color_30", + "value": "#EE8262" + },{ + "name": "bg_color", + "value":"#F0F0F0" + },{ + "name": "color_31", + "value": "#EE6363" + },{ + "name": "color_32", + "value": "#EEB4B4" + },{ + "name": "color_33", + "value": "#D2B48C" + },{ + "name": "color_34", + "value": "#CD9B9B" + },{ + "name": "color_35", + "value": "#5F9EA0" + }, + { + "name": "app_course_chooseweek_bg", + "value": "#E2F7F6" + }, + { + "name": "app_white", + "value": "#ffffff" + }, + { + "name": "app_course_chooseweek_bg2", + "value": "#F1FFFE" + }, + { + "name":"test", + "value":"#3FCAB8" + }, + { + "name":"app_red", + "value":"#d81e06" + }, + { + "name":"color_3", + "value":"#92D261" + }, + { + "name":"app_white_slight", + "value":"#FBFBFB" + }, + { + "name":"app_course_textcolor_blue", + "value":"#13B0F1" + }, + { + "name":"app_gray", + "value":"#999999" + }, + { + "name":"app_gold", + "value":"#FF7A59" + }, + { + "name": "colorAccent", + "value": "#FF4081" + }, + { + "name": "colorPrimary", + "value": "#dc7e2c" + }, + { + "name": "colorSecondary", + "value": "#99000000" + }, + { + "name": "colorAppbarTitle", + "value": "#000000" + }, + { + "name": "colorAppbarBg", + "value": "#ffffff" + }, + { + "name": "colorAppbarSubBg", + "value": "#ffffff" + }, + { + "name": "textColorPrimary", + "value": "#E6000000" + }, + { + "name": "textColorSecondary", + "value": "#99000000" + }, + { + "name": "colorCardViewBg", + "value": "#ffffff" + }, + { + "name": "colorAppBackground", + "value": "#f1f3f5" + }, + { + "name": "colorSearchBarBackground", + "value": "#0C000000" + }, + { + "name": "colorListDivider", + "value": "#33000000" + }, + { + "name": "divider", + "value": "#dddddd" + }, + { + "name": "colorListHeadBackground", + "value": "#77787b" + }, + { + "name": "white", + "value": "#ffffff" + }, + { + "name": "colorPrimaryDark", + "value": "#1e82d2" + }, + { + "name": "black", + "value": "#000000" + }, + { + "name": "green", + "value": "#45b97c" + }, + { + "name": "brown", + "value": "#843900" + } + ] +} \ No newline at end of file diff --git a/ikvStockChart/src/main/resources/base/element/string.json b/ikvStockChart/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..600773da252d8b1331b3b00d90503679e1621e95 --- /dev/null +++ b/ikvStockChart/src/main/resources/base/element/string.json @@ -0,0 +1,48 @@ +{ + "string": [ + { + "name": "ma_normal", + "value": "MA(5, 10, 20)" + }, + { + "name": "ma_highlight", + "value": "●MA5 %1$.2f ●MA10 %2$.2f ●MA20 %3$.2f" + }, + { + "name": "macd_normal", + "value": "MACD(12, 26, 9)" + }, + { + "name": "macd_highlight", + "value": "MACD(12, 26, 9) ●DIFF %1$.2f ●DEA %2$.2f ●MACD %3$.2f" + }, + { + "name": "rsi_normal", + "value": "RSI(6, 12, 24)" + }, + { + "name": "rsi_highlight", + "value": "RSI(6, 12, 24) ●RSI1 %1$.2f ●RSI2 %2$.2f ●RSI3 %3$.2f" + }, + { + "name": "kdj_normal", + "value": "KDJ(9, 3, 3)" + }, + { + "name": "kdj_highlight", + "value": "KDJ(9, 3, 3) ●K %1$.2f ●D %2$.2f ●J %3$.2f" + }, + { + "name": "boll_normal", + "value": "BOLL(20, 2)" + }, + { + "name": "boll_highlight", + "value": "BOLL(20, 2) ●MID %1$.2f ●UPPER %2$.2f ●LOWER %3$.2f" + }, + { + "name": "volume_highlight", + "value": "●MA5 %1$.2f ●MA10 %2$.2f" + } + ] +} diff --git a/ikvStockChart/src/main/resources/base/graphic/but_background.xml b/ikvStockChart/src/main/resources/base/graphic/but_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..7494ae4244a6fa40f5b1f0103084f36573f96a38 --- /dev/null +++ b/ikvStockChart/src/main/resources/base/graphic/but_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ikvStockChart/src/main/resources/base/layout/tab_stockindex.xml b/ikvStockChart/src/main/resources/base/layout/tab_stockindex.xml new file mode 100644 index 0000000000000000000000000000000000000000..57f08ca21f39164c37b192374ed0a55adb50af58 --- /dev/null +++ b/ikvStockChart/src/main/resources/base/layout/tab_stockindex.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleTest.java b/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..77f3676360a4a1a25e518bbc045525dee7a1418b --- /dev/null +++ b/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleTest.java @@ -0,0 +1,9 @@ +package com.wordplat.ikvstockchart; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleUnitTest.java b/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleUnitTest.java deleted file mode 100644 index e915aa8cef28ccf58b83e937d443446e28703714..0000000000000000000000000000000000000000 --- a/ikvStockChart/src/test/java/com/wordplat/ikvstockchart/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.wordplat.ikvstockchart; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 413ff88e0b8c21b88f05871f38a8134f70533976..28a54c11d0cd66cd0903af231e628dc5438fe56d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':ikvStockChart' +include ':entry', ':ikvstockchart'