├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── Project.xml ├── misc.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── apk ├── app-debug.apk └── app-release.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ └── output.json └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── api1 │ │ ├── p1.json │ │ ├── p1_r1.json │ │ ├── p2.json │ │ ├── p3.json │ │ ├── p3_r1.json │ │ └── p3_r2.json │ └── api2 │ │ ├── p1.json │ │ ├── p1_r1.json │ │ ├── p2.json │ │ ├── p3.json │ │ ├── p3_r1.json │ │ └── p3_r2.json │ ├── java │ └── com │ │ └── jidcoo │ │ └── android │ │ └── widgettest │ │ ├── MainActivity.java │ │ ├── custom │ │ ├── CustomCommentModel.java │ │ ├── CustomCommentViewHolder.java │ │ ├── CustomReplyViewHolder.java │ │ ├── CustomUseInLocalActivity.java │ │ ├── CustomViewStyleConfigurator.java │ │ └── SampleCircleImageView.java │ │ └── simple │ │ ├── DefaultUseInLocalActivity.java │ │ ├── DefaultUseWithServerActivity.java │ │ ├── HttpServer.java │ │ └── LocalServer.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ └── round_shape.xml │ ├── layout │ ├── activity_main.xml │ ├── custom_item_comment.xml │ ├── custom_item_reply.xml │ ├── custom_use.xml │ ├── editor.xml │ └── simple_use.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── commentview ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jidcoo │ │ └── android │ │ └── widget │ │ └── commentview │ │ ├── CommentView.java │ │ ├── adapter │ │ └── ViewAdapter.java │ │ ├── callback │ │ ├── CustomCommentItemCallback.java │ │ ├── CustomReplyItemCallback.java │ │ ├── OnCommentLoadMoreCallback.java │ │ ├── OnConvertViewClickCallback.java │ │ ├── OnItemClickCallback.java │ │ ├── OnPullRefreshCallback.java │ │ ├── OnReplyLoadMoreCallback.java │ │ └── OnScrollCallback.java │ │ ├── defaults │ │ ├── DefaultCommentHolder.java │ │ ├── DefaultCommentModel.java │ │ ├── DefaultItemBuilder.java │ │ ├── DefaultReplyHolder.java │ │ └── DefaultViewStyleConfigurator.java │ │ ├── model │ │ ├── AbstractCommentModel.java │ │ ├── CommentEnable.java │ │ ├── PagerEnable.java │ │ └── ReplyEnable.java │ │ ├── operator │ │ ├── AbsAdapterOperator.java │ │ ├── AbsOperator.java │ │ ├── AdapterOperator.java │ │ └── CommentViewOperator.java │ │ ├── utils │ │ └── ViewUtil.java │ │ └── view │ │ ├── CommentListView.java │ │ ├── LoadingMoreFootView.java │ │ ├── RoundAngleImageView.java │ │ ├── ViewHolder.java │ │ └── ViewStyleConfigurator.java │ └── res │ ├── drawable │ ├── ico.png │ └── pxjh.png │ ├── layout │ ├── commentview.xml │ ├── footer_layout.xml │ ├── item_comment.xml │ ├── item_loadmore.xml │ └── item_reply.xml │ └── values │ ├── attr.xml │ ├── ids.xml │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | <<<<<<< HEAD 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | ======= 17 | # Built application files 18 | *.apk 19 | *.aar 20 | *.ap_ 21 | *.aab 22 | 23 | # Files for the ART/Dalvik VM 24 | *.dex 25 | 26 | # Java class files 27 | *.class 28 | 29 | # Generated files 30 | bin/ 31 | gen/ 32 | out/ 33 | # Uncomment the following line in case you need and you don't have the release build type files in your app 34 | # release/ 35 | 36 | # Gradle files 37 | .gradle/ 38 | build/ 39 | 40 | # Local configuration file (sdk path, etc) 41 | local.properties 42 | 43 | # Proguard folder generated by Eclipse 44 | proguard/ 45 | 46 | # Log Files 47 | *.log 48 | 49 | # Android Studio Navigation editor temp files 50 | .navigation/ 51 | 52 | # Android Studio captures folder 53 | captures/ 54 | 55 | # IntelliJ 56 | *.iml 57 | .idea/workspace.xml 58 | .idea/tasks.xml 59 | .idea/gradle.xml 60 | .idea/assetWizardSettings.xml 61 | .idea/dictionaries 62 | .idea/libraries 63 | # Android Studio 3 in .gitignore file. 64 | .idea/caches 65 | .idea/modules.xml 66 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 67 | .idea/navEditor.xml 68 | 69 | # Keystore files 70 | # Uncomment the following lines if you do not want to check your keystore files in. 71 | #*.jks 72 | #*.keystore 73 | 74 | # External native build folder generated in Android Studio 2.2 and later 75 | .externalNativeBuild 76 | .cxx/ 77 | 78 | # Google Services (e.g. APIs or Firebase) 79 | # google-services.json 80 | 81 | # Freeline 82 | freeline.py 83 | freeline/ 84 | freeline_project_description.json 85 | 86 | # fastlane 87 | fastlane/report.xml 88 | fastlane/Preview.html 89 | fastlane/screenshots 90 | fastlane/test_output 91 | fastlane/readme.md 92 | 93 | # Version control 94 | vcs.xml 95 | 96 | # lint 97 | lint/intermediates/ 98 | lint/generated/ 99 | lint/outputs/ 100 | lint/tmp/ 101 | # lint/reports/ 102 | >>>>>>> b7e1793011106eeac618f3468d39750dc0cb4357 103 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | WidgetTest -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommentView 2 | 简洁、高效、可自定义的开源的Android评论控件---CommentView 3 | 4 | ## 控件的使用 5 | ### 1、控件方法介绍 6 | 7 | > **以下是控件对外公布的所有业务方法。** 8 | 9 | | 方法 | 参数 | 说明 | 10 | |--|--|--| 11 | | .callbackBuilder()| \ |获取CallbackBuilder实例设置需要的回调, **无论是否设置回调,都必须调用.callbackBuilder().buildCallback()方法完成初始化。并且初始化必须在loadComplete()调用之前完成。**| | 12 | | .loadComplete()|AbstractCommentModel |**首次加载数据**时调用此方法, **注意:设置回调,设置空数据视图、设置错误视图、设置自定义样式配置器,设置头布局,这5个方法都必须要在loadComplete()这个方法调用之前调用,否则这5个方法失效。**| | 13 | | .loadFailed()|boolean isShowErrorView |首次加载数据失败时调用,boolean参数表示是否显示错误视图。| | 14 | | .refreshComplete()|AbstractCommentModel |刷新数据完成后调用,传入刷新后的数据实体类。该方法调用后OnPullRefreshCallback的complete()方法会被调用。| | 15 | | .refreshFailed()|String msg,boolean isShowErrorView |数据刷新失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnPullRefreshCallback的failure(String msg)方法会被调用。| | 16 | | .loadMoreComplete()|AbstractCommentModel |加载更多评论数据完成后调用,传入一个数据实体类。该方法调用后OnCommentLoadMoreCallback的complete()方法会被调用。| | 17 | | .loadMoreFailed()|String msg,boolean isShowErrorView |加载更多评论数据失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnCommentLoadMoreCallback的failure(String msg)方法会被调用。| | 18 | | .loadMoreReplyComplete()|AbstractCommentModel |加载更多回复数据完成后调用,传入一个数据实体类。该方法调用后OnReplyLoadMoreCallback的complete()方法会被调用。| | 19 | | .loadMoreReplyFailed()|String msg,boolean isShowErrorView |加载更多评论数据失败时调用,msg表示错误信息,boolean参数表示是否显示错误视图。该方法调用后OnReplyLoadMoreCallback的failure(String msg)方法会被调用。| | 20 | | .getCommentList()| \ |获取所有评论数据返回List| | 21 | | .getReplyList()| int position |根据position所在的评论获取所在评论的所有回复数据返回List| | 22 | | .addComment()|C<?extend CommentEnable> comment|添加一条评论数据到当前的评论数据集合中| | 23 | | .addReply()|R<?extend ReplyEnable> comment,int position|添加一条回复数据到position所在的评论的回复数据集合中| | 24 | | .setEmptyView()| View view |为控件设置空数据视图,**注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。**| | 25 | | .setErrorView()| View view |为控件设置错误视图,**注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。**| | 26 | | .addHeaderView()| View view,boolean canClickable |为控件添加头视图,并且设置该视图是否响应点击事件。**注意:此方法建议在loadComplete()方法调用前调用。**| | 27 | | .removeHeaderView()| View view |移除当前控件的对应的头视图| | 28 | | .setViewStyleConfigurator()| ViewStyleConfigurator |设置自定义的样式配置器,**注意:此方法必须在loadComplete()方法调用前调用,也就是说要在首次加载数据前调用,否则此方法无效。**| | 29 | |--|--|--| 30 | 31 | 32 | 33 | ### 2、基本使用 34 | **第一步:引入控件库:** 35 | 36 | 有两种方法: 37 | 38 | **1、远程仓库** 39 | 40 | 在module的build.gradle中添加jcenter仓库: 41 | 42 | ```java 43 | buildscript { 44 | repositories { 45 | jcenter() 46 | } 47 | } 48 | ``` 49 | 然后在dependencies模块中添加依赖即可: 50 | 51 | ```java 52 | implementation 'com.jidcoo.android.widget.commentview:CommentView:1.0.0' 53 | ``` 54 | **2、本地仓库** 55 | 56 | 把源码包下载下来,把commentview库放在与当前module的同级。 57 | 58 | 然后在dependencies模块中添加本地依赖即可: 59 | 60 | ```java 61 | implementation project(path: ':commentview') 62 | ``` 63 | 64 | 65 | **第二步:引入控件:** 66 | 67 | 控件的引入方法有两种: 68 | 69 | 1、XML布局文件中引入 70 | 71 | ```java 72 | 73 | 77 | 78 | 79 | 83 | 84 | ``` 85 | 然后在Activity中实例化控件。 86 | 87 | 2、Java代码中动态创建 88 | 89 | ```java 90 | //控件容器 91 | LinearLayout mContainer; 92 | CommentView commentView=new CommentView(this,mContainer); 93 | 94 | ``` 95 | CommentView的构造方法: 96 | public CommentView(Context context, ViewGroup attachTo) 97 | 98 | 第一个参数是Activity的上下文。第二个参数是ViewGroup,也就是**将控件挂靠在这个指定的布局上**。注意:**当attachTo参数为空时,需要手动把控件添加到布局中,否则控件将不会显示**。 99 | 100 | 101 | **第三步:初始化控件:** 102 | 103 | **设置自定义样式配置器**: 104 | 105 | 如果使用默认样式的话就不需要调用这个方法,如果使用自定义样式配置器时调用该方法必须在loadComplete()调用前调用,否则该方法无效。 106 | 107 | ```java 108 | commentView.setViewStyleConfigurator(你的样式配置器); 109 | ``` 110 | 111 | **设置空数据视图**: 112 | 如果不需要设置空数据视图就不需要调用这个方法,如果需要设置空数据视图时调用该方法必须在loadComplete()调用前调用,否则该方法无效。 113 | ```java 114 | commentView.setEmptyView(你的空数据视图); 115 | ``` 116 | 117 | **设置错误视图**: 118 | 如果不需要设置错误视图就不需要调用这个方法,如果需要设置错误视图时调用该方法必须在loadComplete()调用前调用,否则该方法无效。 119 | ```java 120 | commentView.setErrorView(你的错误视图); 121 | ``` 122 | 123 | **添加控件头视图**: 124 | 如果不需要添加控件头视图就不需要调用这个方法,如果需要添加控件头视图时调用该方法建议在loadComplete()调用前调用。 125 | ```java 126 | commentView.addHeaderView(你的错误视图,是否响应点击事件); 127 | ``` 128 | 129 | **第四步:初始化回调(非常重要,必须初始化):** 130 | 131 | **无论是否需要设置回调,都要调用.buildCallback()方法完成初始化。** 132 | 133 | **并且初始化回调的工作必须要在loadComplete()方法调用之前(即首次加载数据之前)完成,否则控件将无法正常使用。** 134 | 135 | 支持的回调: 136 |

1、CustomCommentItemCallback:自定义评论布局回调

137 |

2、CustomReplyItemCallback:自定义回复布局回调

138 |

3、OnPullRefreshCallback:上拉刷新回调

139 |

4、OnCommentLoadMoreCallback:下拉加载更多评论回调

140 |

5、OnReplyLoadMoreCallback:加载更多回复回调

141 |

6、OnItemClickCallback:Item的点击事件回调

142 |

7、OnScrollCallback:控件滚动事件回调

143 | 144 | 当需要设置回调时: 145 | 146 | **设置完回调后必须调用.buildCallback()方法,否则回调不会生效,控件也无法正常使用。** 147 | 148 | ```java 149 | commentView.callbackBuilder() 150 | .setOnPullRefreshCallback(你的回调实例) 151 | .onItemClickCallback(你的回调实例) 152 | ......设置更多回调 153 | ......设置更多回调 154 | //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效,控件也无法正常显示。 155 | //无论是否设置回调,buildCallback()方法都必须调用。 156 | .buildCallback(); 157 | ``` 158 | 159 | 当不需要设置回调时: 160 | 161 | **必须调用.buildCallback()方法,否则控件也无法正常使用。** 162 | 163 | ```java 164 | commentView.callbackBuilder().buildCallback(); 165 | ``` 166 | 167 | **第五步:设置数据:** 168 | 169 | 当所有的初始化工作都完成后,就可以请求后台返回评论数据或加载本地数据为控件设置数据了。完成设置数据后,控件就能正确显示评论数据了。 170 | 171 | ```java 172 | commentView.loadComplete(你的数据模型实体类); 173 | ``` 174 | -------------------------------------------------------------------------------- /apk/app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jidcoo/CommentView/26df8f9903d0dd664bc975945b17faba2b3fba73/apk/app-debug.apk -------------------------------------------------------------------------------- /apk/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jidcoo/CommentView/26df8f9903d0dd664bc975945b17faba2b3fba73/apk/app-release.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | defaultConfig { 7 | applicationId 8 | minSdkVersion 21 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation 'androidx.appcompat:appcompat:1.1.0' 26 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 27 | implementation 'com.google.code.gson:gson:2.8.6' 28 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' 29 | implementation project(path: ':commentview') 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputType": { 4 | "type": "APK" 5 | }, 6 | "apkData": { 7 | "type": "MAIN", 8 | "splits": [], 9 | "versionCode": 1, 10 | "versionName": "1.0", 11 | "enabled": true, 12 | "outputFile": "app-release.apk", 13 | "fullName": "release", 14 | "baseName": "release" 15 | }, 16 | "path": "app-release.apk", 17 | "properties": {} 18 | } 19 | ] -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/assets/api1/p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 18, 5 | "fid": 1593699394031, 6 | "posterName": "Rust", 7 | "comment": "就这?不会吧。不会吧?", 8 | "date": 1593699394031, 9 | "prizes": 0, 10 | "pid": 0, 11 | "replies": [ 12 | { 13 | "id": 1, 14 | "kid": 1593699394031, 15 | "replierName": "Java", 16 | "reply": "没错,就这……", 17 | "date": 1593742416885, 18 | "prizes": 0, 19 | "pid": 0 20 | }, 21 | { 22 | "id": 2, 23 | "kid": 1593699394031, 24 | "replierName": "Rust", 25 | "reply": "我看过你写的文章,太棒了", 26 | "date": 1593742512674, 27 | "prizes": 0, 28 | "pid": 1 29 | } 30 | ], 31 | "currentPage": 1, 32 | "totalPages": 2, 33 | "totalDataSize": 4, 34 | "pageSize": 2, 35 | "nextPage": 2, 36 | "prefPage": 0 37 | }, 38 | { 39 | "id": 17, 40 | "fid": 1593698842903, 41 | "posterName": "看我发量", 42 | "comment": "少壮不努力,长大做IT。", 43 | "date": 1593698842903, 44 | "prizes": 0, 45 | "pid": 0, 46 | "replies": [ 47 | { 48 | "id": 7, 49 | "kid": 1593698842903, 50 | "replierName": "PHP", 51 | "reply": "同样9年义务教育,为什么你如此秀", 52 | "date": 1593743142836, 53 | "prizes": 0, 54 | "pid": 0 55 | }, 56 | { 57 | "id": 12, 58 | "kid": 1593698842903, 59 | "replierName": "水哥", 60 | "reply": "这边砖厂招人,明天来上班吧", 61 | "date": 1593743910791, 62 | "prizes": 0, 63 | "pid": 0 64 | } 65 | ], 66 | "currentPage": 1, 67 | "totalPages": 1, 68 | "totalDataSize": 2, 69 | "pageSize": 2, 70 | "nextPage": 0, 71 | "prefPage": 0 72 | }, 73 | { 74 | "id": 16, 75 | "fid": 1593698840562, 76 | "posterName": "Python", 77 | "comment": "学完python之后就飞了", 78 | "date": 1593698840562, 79 | "prizes": 0, 80 | "pid": 0, 81 | "replies": [], 82 | "currentPage": 1, 83 | "totalPages": 0, 84 | "totalDataSize": 0, 85 | "pageSize": 2, 86 | "nextPage": 0, 87 | "prefPage": 0 88 | }, 89 | { 90 | "id": 15, 91 | "fid": 1593698838641, 92 | "posterName": "安卓菜鸟", 93 | "comment": "有用过JetPack的没?交流交流", 94 | "date": 1593698838641, 95 | "prizes": 0, 96 | "pid": 0, 97 | "replies": [], 98 | "currentPage": 1, 99 | "totalPages": 0, 100 | "totalDataSize": 0, 101 | "pageSize": 2, 102 | "nextPage": 0, 103 | "prefPage": 0 104 | } 105 | ], 106 | "currentPage": 1, 107 | "totalPages": 3, 108 | "totalDataSize": 10, 109 | "pageSize": 4, 110 | "nextPage": 2, 111 | "prefPage": 0 112 | } -------------------------------------------------------------------------------- /app/src/main/assets/api1/p1_r1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 0, 5 | "fid": 0, 6 | "date": 0, 7 | "prizes": 0, 8 | "pid": 0, 9 | "replies": [ 10 | { 11 | "id": 3, 12 | "kid": 1593699394031, 13 | "replierName": "C++", 14 | "reply": "大佬,带我上车", 15 | "date": 1593742664651, 16 | "prizes": 0, 17 | "pid": 1 18 | }, 19 | { 20 | "id": 4, 21 | "kid": 1593699394031, 22 | "replierName": "C", 23 | "reply": "我带你去搬砖好吧?", 24 | "date": 1593742710247, 25 | "prizes": 0, 26 | "pid": 3 27 | } 28 | ], 29 | "currentPage": 2, 30 | "totalPages": 2, 31 | "totalDataSize": 4, 32 | "pageSize": 2, 33 | "nextPage": 0, 34 | "prefPage": 1 35 | } 36 | ], 37 | "currentPage": 1, 38 | "totalPages": 0, 39 | "totalDataSize": 0, 40 | "pageSize": 0, 41 | "nextPage": 0, 42 | "prefPage": 0 43 | } -------------------------------------------------------------------------------- /app/src/main/assets/api1/p2.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 14, 5 | "fid": 1593698826445, 6 | "posterName": "水哥", 7 | "comment": "Spring源码看完没有……", 8 | "date": 1593698826445, 9 | "prizes": 0, 10 | "pid": 0, 11 | "replies": [], 12 | "currentPage": 1, 13 | "totalPages": 0, 14 | "totalDataSize": 0, 15 | "pageSize": 2, 16 | "nextPage": 0, 17 | "prefPage": 0 18 | }, 19 | { 20 | "id": 13, 21 | "fid": 1593698694541, 22 | "posterName": "Yool", 23 | "comment": "HR问你怎么证明你很牛?", 24 | "date": 1593698694541, 25 | "prizes": 0, 26 | "pid": 0, 27 | "replies": [ 28 | { 29 | "id": 5, 30 | "kid": 1593698694541, 31 | "replierName": "Java", 32 | "reply": "你和他说:JVM是我写的", 33 | "date": 1593742855036, 34 | "prizes": 0, 35 | "pid": 0 36 | } 37 | ], 38 | "currentPage": 1, 39 | "totalPages": 1, 40 | "totalDataSize": 1, 41 | "pageSize": 2, 42 | "nextPage": 0, 43 | "prefPage": 0 44 | }, 45 | { 46 | "id": 12, 47 | "fid": 1593698453866, 48 | "posterName": "PHP", 49 | "comment": "PHP是全世界最好的语言", 50 | "date": 1593698453866, 51 | "prizes": 0, 52 | "pid": 0, 53 | "replies": [], 54 | "currentPage": 1, 55 | "totalPages": 0, 56 | "totalDataSize": 0, 57 | "pageSize": 2, 58 | "nextPage": 0, 59 | "prefPage": 0 60 | }, 61 | { 62 | "id": 11, 63 | "fid": 1593698425154, 64 | "posterName": "Go", 65 | "comment": "这篇文章写的挺好的", 66 | "date": 1593698425154, 67 | "prizes": 0, 68 | "pid": 0, 69 | "replies": [], 70 | "currentPage": 1, 71 | "totalPages": 0, 72 | "totalDataSize": 0, 73 | "pageSize": 2, 74 | "nextPage": 0, 75 | "prefPage": 0 76 | } 77 | ], 78 | "currentPage": 2, 79 | "totalPages": 3, 80 | "totalDataSize": 10, 81 | "pageSize": 4, 82 | "nextPage": 3, 83 | "prefPage": 1 84 | } -------------------------------------------------------------------------------- /app/src/main/assets/api1/p3.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 10, 5 | "fid": 1593698204483, 6 | "posterName": "C++", 7 | "comment": "我是写C++的,STL用的贼6,hh。", 8 | "date": 1593698204483, 9 | "prizes": 0, 10 | "pid": 0, 11 | "replies": [ 12 | { 13 | "id": 8, 14 | "kid": 1593698204483, 15 | "replierName": "PHP", 16 | "reply": "STL是什么?没学过C++。", 17 | "date": 1593743444136, 18 | "prizes": 0, 19 | "pid": 0 20 | }, 21 | { 22 | "id": 9, 23 | "kid": 1593698204483, 24 | "replierName": "Python", 25 | "reply": "就是C++的标准模板库呀,楼主精通STL,大佬大佬", 26 | "date": 1593743522943, 27 | "prizes": 0, 28 | "pid": 8 29 | } 30 | ], 31 | "currentPage": 1, 32 | "totalPages": 3, 33 | "totalDataSize": 5, 34 | "pageSize": 2, 35 | "nextPage": 2, 36 | "prefPage": 0 37 | }, 38 | { 39 | "id": 9, 40 | "fid": 1593697988950, 41 | "posterName": "Java", 42 | "comment": "大家好,我是Java开发者", 43 | "date": 1593697988950, 44 | "prizes": 0, 45 | "pid": 0, 46 | "replies": [], 47 | "currentPage": 1, 48 | "totalPages": 0, 49 | "totalDataSize": 0, 50 | "pageSize": 2, 51 | "nextPage": 0, 52 | "prefPage": 0 53 | } 54 | ], 55 | "currentPage": 3, 56 | "totalPages": 3, 57 | "totalDataSize": 10, 58 | "pageSize": 4, 59 | "nextPage": 0, 60 | "prefPage": 2 61 | } -------------------------------------------------------------------------------- /app/src/main/assets/api1/p3_r1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 0, 5 | "fid": 0, 6 | "date": 0, 7 | "prizes": 0, 8 | "pid": 0, 9 | "replies": [ 10 | { 11 | "id": 10, 12 | "kid": 1593698204483, 13 | "replierName": "Java", 14 | "reply": "Java不香吗?", 15 | "date": 1593743708231, 16 | "prizes": 0, 17 | "pid": 0 18 | }, 19 | { 20 | "id": 11, 21 | "kid": 1593698204483, 22 | "replierName": "C++", 23 | "reply": "C++是世界上最好的语言,不接受反驳!", 24 | "date": 1593743764549, 25 | "prizes": 0, 26 | "pid": 10 27 | } 28 | ], 29 | "currentPage": 2, 30 | "totalPages": 3, 31 | "totalDataSize": 5, 32 | "pageSize": 2, 33 | "nextPage": 3, 34 | "prefPage": 1 35 | } 36 | ], 37 | "currentPage": 1, 38 | "totalPages": 0, 39 | "totalDataSize": 0, 40 | "pageSize": 0, 41 | "nextPage": 0, 42 | "prefPage": 0 43 | } -------------------------------------------------------------------------------- /app/src/main/assets/api1/p3_r2.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "id": 0, 5 | "fid": 0, 6 | "date": 0, 7 | "prizes": 0, 8 | "pid": 0, 9 | "replies": [ 10 | { 11 | "id": 13, 12 | "kid": 1593698204483, 13 | "replierName": "PHP", 14 | "reply": "这不是我PHP的口头禅吗?", 15 | "date": 1593744004615, 16 | "prizes": 0, 17 | "pid": 11 18 | } 19 | ], 20 | "currentPage": 3, 21 | "totalPages": 3, 22 | "totalDataSize": 5, 23 | "pageSize": 2, 24 | "nextPage": 0, 25 | "prefPage": 2 26 | } 27 | ], 28 | "currentPage": 1, 29 | "totalPages": 0, 30 | "totalDataSize": 0, 31 | "pageSize": 0, 32 | "nextPage": 0, 33 | "prefPage": 0 34 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "posterName": "Rust", 5 | "data": "就这?不会吧。不会吧?", 6 | "replies": [ 7 | { 8 | "replierName": "Java", 9 | "data": "没错,就这……" 10 | }, 11 | { 12 | "replierName": "Rust", 13 | "data": "我看过你写的文章,太棒了" 14 | } 15 | ], 16 | "currentPage": 1, 17 | "totalPages": 2, 18 | "totalDataSize": 4, 19 | "pageSize": 2, 20 | "nextPage": 2, 21 | "prefPage": 0 22 | }, 23 | { 24 | "posterName": "看我发量", 25 | "data": "少壮不努力,长大做IT。", 26 | "replies": [ 27 | { 28 | "replierName": "PHP", 29 | "data": "同样9年义务教育,为什么你如此秀" 30 | }, 31 | { 32 | "replierName": "水哥", 33 | "data": "这边砖厂招人,明天来上班吧" 34 | } 35 | ], 36 | "currentPage": 1, 37 | "totalPages": 1, 38 | "totalDataSize": 2, 39 | "pageSize": 2, 40 | "nextPage": 0, 41 | "prefPage": 0 42 | }, 43 | { 44 | "posterName": "Python", 45 | "data": "学完python之后就飞了", 46 | "replies": [], 47 | "currentPage": 1, 48 | "totalPages": 0, 49 | "totalDataSize": 0, 50 | "pageSize": 2, 51 | "nextPage": 0, 52 | "prefPage": 0 53 | }, 54 | { 55 | "posterName": "Rust", 56 | "data": "日常打卡", 57 | "replies": [ 58 | { 59 | "replierName": "小助手", 60 | "data": "我看到你昨天没打卡耶" 61 | } 62 | ], 63 | "currentPage": 1, 64 | "totalPages": 1, 65 | "totalDataSize": 1, 66 | "pageSize": 2, 67 | "nextPage": 0, 68 | "prefPage": 0 69 | }, 70 | { 71 | "posterName": "ACC坐骑", 72 | "data": "有类型的代码吗", 73 | "replies": [], 74 | "currentPage": 1, 75 | "totalPages": 0, 76 | "totalDataSize": 0, 77 | "pageSize": 2, 78 | "nextPage": 0, 79 | "prefPage": 0 80 | }, 81 | { 82 | "posterName": "安卓菜鸟", 83 | "data": "有用过JetPack的没?交流交流", 84 | "replies": [], 85 | "currentPage": 1, 86 | "totalPages": 0, 87 | "totalDataSize": 0, 88 | "pageSize": 2, 89 | "nextPage": 0, 90 | "prefPage": 0 91 | } 92 | ], 93 | "currentPage": 1, 94 | "totalPages": 3, 95 | "totalDataSize": 10, 96 | "pageSize": 4, 97 | "nextPage": 2, 98 | "prefPage": 0 99 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p1_r1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "replies": [ 5 | { 6 | "replierName": "C++", 7 | "data": "大佬,带我上车" 8 | }, 9 | { 10 | "replierName": "C", 11 | "data": "我带你去搬砖好吧?" 12 | } 13 | ], 14 | "currentPage": 2, 15 | "totalPages": 2, 16 | "totalDataSize": 4, 17 | "pageSize": 2, 18 | "nextPage": 0, 19 | "prefPage": 1 20 | } 21 | ], 22 | "currentPage": 1, 23 | "totalPages": 0, 24 | "totalDataSize": 0, 25 | "pageSize": 0, 26 | "nextPage": 0, 27 | "prefPage": 0 28 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p2.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "posterName": "水哥", 5 | "data": "Spring源码看完没有……", 6 | "replies": [], 7 | "currentPage": 1, 8 | "totalPages": 0, 9 | "totalDataSize": 0, 10 | "pageSize": 2, 11 | "nextPage": 0, 12 | "prefPage": 0 13 | }, 14 | { 15 | "posterName": "Yool", 16 | "data": "HR问你怎么证明你很牛?", 17 | "replies": [ 18 | { 19 | "replierName": "Java", 20 | "data": "你和他说:JVM是我写的" 21 | } 22 | ], 23 | "currentPage": 1, 24 | "totalPages": 1, 25 | "totalDataSize": 1, 26 | "pageSize": 2, 27 | "nextPage": 0, 28 | "prefPage": 0 29 | }, 30 | { 31 | "posterName": "PHP", 32 | "data": "PHP是全世界最好的语言", 33 | "replies": [], 34 | "currentPage": 1, 35 | "totalPages": 0, 36 | "totalDataSize": 0, 37 | "pageSize": 2, 38 | "nextPage": 0, 39 | "prefPage": 0 40 | }, 41 | { 42 | "posterName": "Go", 43 | "data": "这篇文章写的挺好的", 44 | "replies": [], 45 | "currentPage": 1, 46 | "totalPages": 0, 47 | "totalDataSize": 0, 48 | "pageSize": 2, 49 | "nextPage": 0, 50 | "prefPage": 0 51 | } 52 | ], 53 | "currentPage": 2, 54 | "totalPages": 3, 55 | "totalDataSize": 10, 56 | "pageSize": 4, 57 | "nextPage": 3, 58 | "prefPage": 1 59 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p3.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "posterName": "C++", 5 | "data": "我是写C++的,STL用的贼6,hh。", 6 | "replies": [ 7 | { 8 | "replierName": "PHP", 9 | "data": "STL是什么?没学过C++。" 10 | }, 11 | { 12 | "replierName": "Python", 13 | "data": "就是C++的标准模板库呀,楼主精通STL,大佬大佬" 14 | } 15 | ], 16 | "currentPage": 1, 17 | "totalPages": 3, 18 | "totalDataSize": 5, 19 | "pageSize": 2, 20 | "nextPage": 2, 21 | "prefPage": 0 22 | }, 23 | { 24 | "posterName": "Java", 25 | "data": "大家好,我是Java开发者", 26 | "replies": [], 27 | "currentPage": 1, 28 | "totalPages": 0, 29 | "totalDataSize": 0, 30 | "pageSize": 2, 31 | "nextPage": 0, 32 | "prefPage": 0 33 | } 34 | ], 35 | "currentPage": 3, 36 | "totalPages": 3, 37 | "totalDataSize": 10, 38 | "pageSize": 4, 39 | "nextPage": 0, 40 | "prefPage": 2 41 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p3_r1.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "replies": [ 5 | { 6 | "replierName": "Java", 7 | "data": "Java不香吗?" 8 | }, 9 | { 10 | "replierName": "C++", 11 | "data": "C++是世界上最好的语言,不接受反驳!" 12 | } 13 | ], 14 | "currentPage": 2, 15 | "totalPages": 3, 16 | "totalDataSize": 5, 17 | "pageSize": 2, 18 | "nextPage": 3, 19 | "prefPage": 1 20 | } 21 | ], 22 | "currentPage": 1, 23 | "totalPages": 0, 24 | "totalDataSize": 0, 25 | "pageSize": 0, 26 | "nextPage": 0, 27 | "prefPage": 0 28 | } -------------------------------------------------------------------------------- /app/src/main/assets/api2/p3_r2.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": [ 3 | { 4 | "replies": [ 5 | { 6 | "replierName": "PHP", 7 | "data": "这不是我PHP的口头禅吗?" 8 | } 9 | ], 10 | "currentPage": 3, 11 | "totalPages": 3, 12 | "totalDataSize": 5, 13 | "pageSize": 2, 14 | "nextPage": 0, 15 | "prefPage": 2 16 | } 17 | ], 18 | "currentPage": 1, 19 | "totalPages": 0, 20 | "totalDataSize": 0, 21 | "pageSize": 0, 22 | "nextPage": 0, 23 | "prefPage": 0 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | 11 | import com.jidcoo.android.widgettest.custom.CustomUseInLocalActivity; 12 | import com.jidcoo.android.widgettest.simple.DefaultUseInLocalActivity; 13 | import com.jidcoo.android.widgettest.simple.DefaultUseWithServerActivity; 14 | 15 | public class MainActivity extends AppCompatActivity implements View.OnClickListener { 16 | private Button button, button1, button2; 17 | 18 | @Override 19 | protected void onCreate(@Nullable Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | button = findViewById(R.id.button); 23 | button1 = findViewById(R.id.button1); 24 | button2 = findViewById(R.id.button2); 25 | button.setOnClickListener(this); 26 | button1.setOnClickListener(this); 27 | button2.setOnClickListener(this); 28 | } 29 | 30 | @Override 31 | public void onClick(View v) { 32 | Intent intent = null; 33 | switch (v.getId()) { 34 | case R.id.button: 35 | intent = new Intent(MainActivity.this, DefaultUseWithServerActivity.class); 36 | startActivity(intent); 37 | break; 38 | case R.id.button1: 39 | intent = new Intent(MainActivity.this, DefaultUseInLocalActivity.class); 40 | startActivity(intent); 41 | break; 42 | case R.id.button2: 43 | intent = new Intent(MainActivity.this, CustomUseInLocalActivity.class); 44 | startActivity(intent); 45 | break; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/CustomCommentModel.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | 4 | import com.jidcoo.android.widget.commentview.model.AbstractCommentModel; 5 | import com.jidcoo.android.widget.commentview.model.CommentEnable; 6 | import com.jidcoo.android.widget.commentview.model.ReplyEnable; 7 | 8 | import java.util.List; 9 | 10 | public class CustomCommentModel extends AbstractCommentModel { 11 | public List comments; 12 | 13 | @Override 14 | public List getComments() { 15 | return comments; 16 | } 17 | 18 | public class CustomComment extends CommentEnable { 19 | public List replies; 20 | public String posterName; 21 | public String data; 22 | 23 | public CustomComment() { 24 | 25 | } 26 | 27 | public void setReplies(List replies) { 28 | this.replies = replies; 29 | } 30 | 31 | public String getPosterName() { 32 | return posterName; 33 | } 34 | 35 | public void setPosterName(String posterName) { 36 | this.posterName = posterName; 37 | } 38 | 39 | public String getData() { 40 | return data; 41 | } 42 | 43 | public void setData(String data) { 44 | this.data = data; 45 | } 46 | 47 | 48 | @Override 49 | public List getReplies() { 50 | return replies; 51 | } 52 | 53 | 54 | public class CustomReply extends ReplyEnable { 55 | public String replierName; 56 | public String data; 57 | 58 | public CustomReply() { 59 | 60 | } 61 | 62 | public String getReplierName() { 63 | return replierName; 64 | } 65 | 66 | public void setReplierName(String replierName) { 67 | this.replierName = replierName; 68 | } 69 | 70 | public String getData() { 71 | return data; 72 | } 73 | 74 | public void setData(String data) { 75 | this.data = data; 76 | } 77 | 78 | 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/CustomCommentViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import com.jidcoo.android.widgettest.R; 7 | 8 | public class CustomCommentViewHolder { 9 | public TextView userName, prizes, comment; 10 | public SampleCircleImageView ico; 11 | 12 | public CustomCommentViewHolder(View view) { 13 | userName = view.findViewById(R.id.user); 14 | prizes = view.findViewById(R.id.prizes); 15 | comment = view.findViewById(R.id.data); 16 | ico = view.findViewById(R.id.ico); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/CustomReplyViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | import android.view.View; 4 | import android.widget.TextView; 5 | 6 | import com.jidcoo.android.widget.commentview.view.ViewHolder; 7 | import com.jidcoo.android.widgettest.R; 8 | 9 | public class CustomReplyViewHolder extends ViewHolder { 10 | public TextView userName, prizes, reply; 11 | 12 | public CustomReplyViewHolder(View view) { 13 | super(view); 14 | userName = view.findViewById(R.id.user); 15 | prizes = view.findViewById(R.id.prizes); 16 | reply = view.findViewById(R.id.data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/CustomUseInLocalActivity.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.Toast; 11 | 12 | import androidx.annotation.Nullable; 13 | import androidx.appcompat.app.AppCompatActivity; 14 | 15 | import com.google.gson.Gson; 16 | import com.jidcoo.android.widget.commentview.CommentView; 17 | import com.jidcoo.android.widget.commentview.callback.CustomCommentItemCallback; 18 | import com.jidcoo.android.widget.commentview.callback.CustomReplyItemCallback; 19 | import com.jidcoo.android.widget.commentview.callback.OnCommentLoadMoreCallback; 20 | import com.jidcoo.android.widget.commentview.callback.OnItemClickCallback; 21 | import com.jidcoo.android.widget.commentview.callback.OnPullRefreshCallback; 22 | import com.jidcoo.android.widget.commentview.callback.OnReplyLoadMoreCallback; 23 | import com.jidcoo.android.widgettest.R; 24 | import com.jidcoo.android.widgettest.simple.LocalServer; 25 | 26 | import java.lang.ref.WeakReference; 27 | 28 | /** 29 | * 对于CommentView的自定义数据类型和布局的使用实例(使用本地测试数据) 30 | * 使用自定义样式配置器,自定义数据模型,自定义布局 31 | * 注意:使用自定义数据类型就必须自定义布局实现,否则会抛出数据模型的java.lang.ClassCastException异常

32 | * 33 | * @author Jidcoo 34 | */ 35 | public class CustomUseInLocalActivity extends AppCompatActivity { 36 | private CommentView commentView; 37 | private Gson gson; 38 | private LocalServer localServer; 39 | private LinearLayout mContainer; 40 | 41 | // 42 | private static class ActivityHandler extends Handler { 43 | private final WeakReference mActivity; 44 | 45 | public ActivityHandler(CustomUseInLocalActivity activity) { 46 | mActivity = new WeakReference(activity); 47 | } 48 | 49 | @Override 50 | public void handleMessage(Message msg) { 51 | CustomUseInLocalActivity activity = mActivity.get(); 52 | if (activity != null) { 53 | switch (msg.what) { 54 | case 1: 55 | //commentView.loadFailed(true);//实际网络请求中如果加载失败调用此方法 56 | activity.commentView.loadComplete(activity.gson.fromJson((String) msg.obj, CustomCommentModel.class)); 57 | break; 58 | case 2: 59 | //commentView.refreshFailed();//实际网络请求中如果加载失败调用此方法 60 | activity.commentView.refreshComplete(activity.gson.fromJson((String) msg.obj, CustomCommentModel.class)); 61 | break; 62 | case 3: 63 | //commentView.loadFailed();//实际网络请求中如果加载失败调用此方法 64 | activity.commentView.loadMoreComplete(activity.gson.fromJson((String) msg.obj, CustomCommentModel.class)); 65 | break; 66 | case 4: 67 | //commentView.loadMoreReplyFailed();//实际网络请求中如果加载失败调用此方法 68 | activity.commentView.loadMoreReplyComplete(activity.gson.fromJson((String) msg.obj, CustomCommentModel.class)); 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | 75 | private final ActivityHandler activityHandler = new ActivityHandler(this); 76 | // 77 | 78 | @Override 79 | protected void onCreate(@Nullable Bundle savedInstanceState) { 80 | super.onCreate(savedInstanceState); 81 | setContentView(R.layout.custom_use); 82 | gson = new Gson(); 83 | setTitle("WidgetTest(仿微信)"); 84 | localServer = new LocalServer(this, "api2"); 85 | mContainer = findViewById(R.id.container); 86 | commentView = new CommentView(this, mContainer); 87 | //设置空视图 88 | //commentView.setEmptyView(view); 89 | //设置错误视图 90 | //commentView.setErrorView(view); 91 | //添加控件头布局 92 | // commentView.addHeaderView(); 93 | commentView.setViewStyleConfigurator(new CustomViewStyleConfigurator(this)); 94 | 95 | commentView.callbackBuilder() 96 | //自定义评论布局(必须使用ViewHolder机制)--CustomCommentItemCallback 泛型C为自定义评论数据类 97 | .customCommentItem(new CustomCommentItemCallback() { 98 | @Override 99 | public View buildCommentItem(int groupPosition, CustomCommentModel.CustomComment comment, LayoutInflater inflater, View convertView, ViewGroup parent) { 100 | //使用方法就像adapter里面的getView()方法一样 101 | final CustomCommentViewHolder holder; 102 | if (convertView == null) { 103 | //使用自定义布局 104 | convertView = inflater.inflate(R.layout.custom_item_comment, parent, false); 105 | holder = new CustomCommentViewHolder(convertView); 106 | //必须使用ViewHolder机制 107 | convertView.setTag(holder); 108 | } else { 109 | holder = (CustomCommentViewHolder) convertView.getTag(); 110 | } 111 | holder.prizes.setText("100"); 112 | holder.userName.setText(comment.getPosterName()); 113 | holder.comment.setText(comment.getData()); 114 | return convertView; 115 | } 116 | }) 117 | //自定义评论布局(必须使用ViewHolder机制) 118 | // 并且自定义ViewHolder类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder 119 | // --CustomReplyItemCallback 泛型R为自定义回复数据类 120 | .customReplyItem(new CustomReplyItemCallback() { 121 | @Override 122 | public View buildReplyItem(int groupPosition, int childPosition, boolean isLastReply, CustomCommentModel.CustomComment.CustomReply reply, LayoutInflater inflater, View convertView, ViewGroup parent) { 123 | //使用方法就像adapter里面的getView()方法一样 124 | //此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错 125 | CustomReplyViewHolder holder = null; 126 | //此类必须继承自com.jidcoo.android.widget.commentview.view.ViewHolder,否则报错 127 | if (convertView == null) { 128 | //使用自定义布局 129 | convertView = inflater.inflate(R.layout.custom_item_reply, parent, false); 130 | holder = new CustomReplyViewHolder(convertView); 131 | //必须使用ViewHolder机制 132 | convertView.setTag(holder); 133 | } else { 134 | holder = (CustomReplyViewHolder) convertView.getTag(); 135 | } 136 | holder.userName.setText(reply.getReplierName()); 137 | holder.reply.setText(reply.getData()); 138 | holder.prizes.setText("100"); 139 | return convertView; 140 | } 141 | }) 142 | //下拉刷新回调 143 | .setOnPullRefreshCallback(new MyOnPullRefreshCallback()) 144 | //评论、回复Item的点击回调(点击事件回调) 145 | .setOnItemClickCallback(new MyOnItemClickCallback()) 146 | //回复数据加载更多回调(加载更多回复) 147 | .setOnReplyLoadMoreCallback(new MyOnReplyLoadMoreCallback()) 148 | //上拉加载更多回调(加载更多评论数据) 149 | .setOnCommentLoadMoreCallback(new MyOnCommentLoadMoreCallback()) 150 | //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效 151 | .buildCallback(); 152 | load(1, 1); 153 | } 154 | 155 | 156 | private void load(int code, int handlerId) { 157 | localServer.get(code, activityHandler, handlerId); 158 | } 159 | 160 | 161 | /** 162 | * 下拉刷新回调类 163 | */ 164 | class MyOnPullRefreshCallback implements OnPullRefreshCallback { 165 | 166 | @Override 167 | public void refreshing() { 168 | load(1, 2); 169 | 170 | 171 | } 172 | 173 | @Override 174 | public void complete() { 175 | //加载完成后的操作 176 | } 177 | 178 | @Override 179 | public void failure(String msg) { 180 | 181 | } 182 | } 183 | 184 | 185 | /** 186 | * 上拉加载更多回调类 187 | */ 188 | class MyOnCommentLoadMoreCallback implements OnCommentLoadMoreCallback { 189 | 190 | @Override 191 | public void loading(int currentPage, int willLoadPage, boolean isLoadedAllPages) { 192 | //因为测试数据写死了,所以这里的逻辑也是写死的 193 | if (!isLoadedAllPages) { 194 | if (willLoadPage == 2) { 195 | load(2, 3); 196 | } else if (willLoadPage == 3) { 197 | load(3, 3); 198 | } 199 | } 200 | } 201 | 202 | @Override 203 | public void complete() { 204 | //加载完成后的操作 205 | } 206 | 207 | @Override 208 | public void failure(String msg) { 209 | } 210 | } 211 | 212 | /** 213 | * 回复加载更多回调类 214 | */ 215 | class MyOnReplyLoadMoreCallback implements OnReplyLoadMoreCallback { 216 | 217 | 218 | @Override 219 | public void loading(CustomCommentModel.CustomComment.CustomReply reply, int willLoadPage) { 220 | if (willLoadPage == 2) { 221 | load(5, 4); 222 | } else if (willLoadPage == 3) { 223 | load(6, 4); 224 | } 225 | } 226 | 227 | @Override 228 | public void complete() { 229 | 230 | } 231 | 232 | @Override 233 | public void failure(String msg) { 234 | 235 | } 236 | } 237 | 238 | /** 239 | * 点击事件回调 240 | */ 241 | class MyOnItemClickCallback implements OnItemClickCallback { 242 | 243 | 244 | @Override 245 | public void commentItemOnClick(int position, CustomCommentModel.CustomComment comment, View view) { 246 | Toast.makeText(CustomUseInLocalActivity.this, "你点击的评论:" + comment.getData(), Toast.LENGTH_SHORT).show(); 247 | } 248 | 249 | @Override 250 | public void replyItemOnClick(int c_position, int r_position, CustomCommentModel.CustomComment.CustomReply reply, View view) { 251 | Toast.makeText(CustomUseInLocalActivity.this, "你点击的回复:" + reply.getData(), Toast.LENGTH_SHORT).show(); 252 | } 253 | } 254 | 255 | } 256 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/CustomViewStyleConfigurator.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.Typeface; 6 | 7 | import com.jidcoo.android.widget.commentview.utils.ViewUtil; 8 | import com.jidcoo.android.widget.commentview.view.ViewStyleConfigurator; 9 | 10 | 11 | public class CustomViewStyleConfigurator extends ViewStyleConfigurator { 12 | 13 | public CustomViewStyleConfigurator(Context context) { 14 | /** 15 | * 下拉刷新控件颜色 16 | */ 17 | refreshViewColor = "#D81B60"; 18 | //*********//回复加载更多控件样式//*************// 19 | //Part1// 20 | lmv_showText = "展开更多回复"; 21 | lmv_textSize = 14; 22 | lmv_textColor = "#D81B60"; 23 | lmv_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); 24 | //Part2// 25 | lmv_loading_showText = "加载中"; 26 | lmv_loading_textSize = 14; 27 | lmv_loading_textColor = "#666666"; 28 | lmv_loading_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); 29 | lmv_loading_progressBarColor = "#666666"; 30 | lmv_loading_progressBarSize = ViewUtil.dpToPx(14, context); 31 | //Part3// 32 | lmv_adjustMargins = true; 33 | lmv_adjustMarginsLeft = ViewUtil.dpToPx(52, context);//dp 34 | lmv_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp 35 | lmv_adjustMarginsRight = 0;//dp 36 | lmv_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp 37 | //*********//回复加载更多控件样式//*************// 38 | //*********//分割线总样式//*************// 39 | dividerColor = "#00000000"; 40 | dividerHeight = ViewUtil.dpToPx(1, context);//dp 41 | //*********//分割线总样式//*************// 42 | //*********//回复Item最后一项的下划线//*************// 43 | // isDrawChildDivider = false; 44 | // c_divider_adjustMargins = true; 45 | // c_divider_adjustMarginsLeft = ViewUtil.dpToPx(88, context);//dp 46 | // c_divider_adjustMarginsTop = ViewUtil.dpToPx(5, context);//dp 47 | // c_divider_adjustMarginsRight = 0;//dp 48 | // c_divider_adjustMarginsBottom = ViewUtil.dpToPx(5, context);//dp 49 | // //*********//回复Item最后一项的下划线//*************// 50 | // //*********//评论Item的分割线//*************// 51 | // f_divider_adjustMargins = false; 52 | // f_divider_adjustMarginsLeft = 0;//dp 53 | // f_divider_adjustMarginsTop = 0;//dp 54 | // f_divider_adjustMarginsRight = 0;//dp 55 | // f_divider_adjustMarginsBottom = 0;//dp 56 | //*********//评论Item的分割线//*************// 57 | //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// 58 | lm_footerProgressBarSize = ViewUtil.dpToPx(20, context);//dp 59 | lm_footerProgressBarColor = "#666666"; 60 | lm_footerProgressBar_adjustMargins = false; 61 | lm_footerProgressBar_adjustMarginsLeft = 0;//dp 62 | lm_footerProgressBar_adjustMarginsTop = 0;//dp 63 | lm_footerProgressBar_adjustMarginsRight = 0;//dp 64 | lm_footerProgressBar_adjustMarginsBottom = 0;//dp 65 | //*********//底部上拉加载更多圆形ProgressBar的大小和颜色//*************// 66 | 67 | 68 | //*********//底部上拉加载更多完成后的textView//*************// 69 | //文本,颜色、大小、样式、边距 70 | lm_footer_text = "到底了(⊙o⊙)!"; 71 | lm_footer_textColor = "#c3c8cb"; 72 | lm_footer_textSize = 14; 73 | lm_footer_textStyle = Typeface.defaultFromStyle(Typeface.NORMAL); 74 | lm_footer_text_adjustMargins = false; 75 | lm_footer_text_adjustMarginsLeft = 0;//dp 76 | lm_footer_text_adjustMarginsTop = 0;//dp 77 | lm_footer_text_adjustMarginsRight = 0;//dp 78 | lm_footer_text_adjustMarginsBottom = 0;//dp 79 | //*********//底部上拉加载更多完成后的textView//*************// 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/custom/SampleCircleImageView.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.custom; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.Canvas; 8 | import android.graphics.Paint; 9 | import android.graphics.PorterDuff; 10 | import android.graphics.PorterDuffXfermode; 11 | import android.graphics.RectF; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | 15 | import com.jidcoo.android.widgettest.R; 16 | 17 | /** 18 | * Created by Danxx on 2016/7/29. 19 | * 最简单的方式实现圆角图片 20 | * 此控件来自 21 | * https://github.com/Dawish/AndroidLearn/blob/master/AppLibrary/src/main/java/danxx/library/widget/SampleCircleImageView.java 22 | */ 23 | public class SampleCircleImageView extends View { 24 | /** 25 | * 默认圆角大小 26 | */ 27 | private static final int DEFUALT_RADIUS = 20; 28 | /** 29 | * 源图片 30 | */ 31 | private Bitmap mSrc; 32 | /** 33 | * 圆角大小,默认为20 34 | */ 35 | private int mRadius = DEFUALT_RADIUS; 36 | /** 37 | * 控件的宽度 38 | */ 39 | private int mWidth; 40 | /** 41 | * 控件的高度 42 | */ 43 | private int mHeight; 44 | private Context mContext; 45 | 46 | 47 | public SampleCircleImageView(Context context) { 48 | super(context); 49 | init(context, null, 0); 50 | } 51 | 52 | public SampleCircleImageView(Context context, Bitmap bitmap) { 53 | super(context); 54 | this.mSrc = bitmap; 55 | init(context, null, 0); 56 | } 57 | 58 | public SampleCircleImageView(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | init(context, attrs, 0); 61 | } 62 | 63 | public SampleCircleImageView(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | init(context, attrs, defStyleAttr); 66 | } 67 | 68 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 69 | mContext = context; 70 | if (attrs != null) { 71 | /**Load the styled attributes and set their properties**/ 72 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SampleCircleImageView, defStyleAttr, 0); 73 | mSrc = BitmapFactory.decodeResource(context.getResources(), typedArray.getResourceId(R.styleable.SampleCircleImageView_src, 0)); 74 | mRadius = (int) typedArray.getDimension(R.styleable.SampleCircleImageView_radius, dp2px(DEFUALT_RADIUS)); 75 | typedArray.recycle(); 76 | } 77 | 78 | } 79 | 80 | /** 81 | * 测量控件大小 82 | * 83 | * @param widthMeasureSpec 84 | * @param heightMeasureSpec 85 | */ 86 | @Override 87 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 88 | // super.onMeasure(widthMeasureSpec, heightMeasureSpec); 89 | /** 90 | * 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。 91 | * 三种测量模式解释: 92 | * UNSPECIFIED:父布局没有给子布局任何限制,子布局可以任意大小。 93 | * EXACTLY:父布局决定子布局的确切大小。不论子布局多大,它都必须限制在这个界限里。match_parent 94 | * AT_MOST:此时子控件尺寸只要不超过父控件允许的最大尺寸,子布局可以根据自己的大小选择任意大小。wrap_content 95 | * 96 | * 简单的映射关系: 97 | * wrap_content -> MeasureSpec.AT_MOST 98 | * match_parent -> MeasureSpec.EXACTLY 99 | * 具体值 -> MeasureSpec.EXACTLY 100 | */ 101 | 102 | /**获取宽高的测量模式**/ 103 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 104 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 105 | /**获取宽高的尺寸**/ 106 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 107 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 108 | /** 109 | * 测量宽度 110 | */ 111 | if (widthSpecMode == MeasureSpec.EXACTLY) { //宽为具体值或者是填满父控件就直接赋值 match_parent , accurate 112 | mWidth = widthSpecSize; 113 | } else { 114 | /**图片显示时原始大小**/ 115 | int srcWidth = mSrc.getWidth() + getPaddingLeft() + getPaddingRight(); 116 | if (widthSpecMode == MeasureSpec.AT_MOST) { //wrap_content,子控件不能超过父控件,此时我们取传递过来的大小和图片本身大小的小者 117 | mWidth = Math.min(widthSpecSize, srcWidth); 118 | } else { 119 | //没有要求,可以随便大小 120 | mWidth = srcWidth; 121 | } 122 | } 123 | 124 | /** 125 | * 测量高度,逻辑跟测量宽度是一样的 126 | */ 127 | if (heightSpecMode == MeasureSpec.EXACTLY) { //match_parent , accurate 128 | mHeight = heightSpecSize; 129 | } else { 130 | /**图片显示时原始大小**/ 131 | int srcHeigth = mSrc.getHeight() + getPaddingTop() + getPaddingBottom(); 132 | if (heightSpecMode == MeasureSpec.AT_MOST) { //wrap_content 133 | mHeight = Math.min(heightSpecSize, srcHeigth); 134 | } else { 135 | //没有要求,可以随便大小 136 | mHeight = srcHeigth; 137 | } 138 | } 139 | setMeasuredDimension(mWidth, mHeight); 140 | } 141 | 142 | /** 143 | * 绘制控件 144 | * 145 | * @param canvas 146 | */ 147 | @Override 148 | protected void onDraw(Canvas canvas) { 149 | // super.onDraw(canvas); 150 | canvas.drawBitmap(createRoundConerImage(mSrc), 0, 0, null); 151 | } 152 | 153 | /** 154 | * 设置圆角大小 155 | * 156 | * @param radius 157 | */ 158 | public void setRadius(int radius) { 159 | this.mRadius = radius; 160 | } 161 | 162 | /** 163 | * 设置图片 164 | * 165 | * @param bitmap 166 | */ 167 | public void setSrc(Bitmap bitmap) { 168 | this.mSrc = bitmap; 169 | //requestLayout(); 170 | invalidate(); 171 | } 172 | 173 | /** 174 | * 根据给定的图片和已经测量出来的宽高来绘制圆角图形 175 | * 原理: 176 | * 基本原理就是先画一个圆角的图形出来,然后在圆角图形上画我们的源图片, 177 | * 圆角图形跟我们的源图片堆叠时我们取交集并显示上层的图形 178 | * 原理就是这样,很简单。 179 | */ 180 | private Bitmap createRoundConerImage(Bitmap source) { 181 | final Paint paint = new Paint(); 182 | /**开启抗锯齿**/ 183 | paint.setAntiAlias(true); 184 | /****/ 185 | Bitmap target = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 186 | /** 187 | * Construct a canvas with the specified bitmap to draw into. The bitmapmust be mutable 188 | * 以bitmap对象创建一个画布,则将内容都绘制在bitmap上,bitmap不得为null; 189 | */ 190 | Canvas canvas = new Canvas(target); 191 | /**新建一个矩形绘制区域,并给出左上角和右下角的坐标**/ 192 | RectF rect = new RectF(0, 0, mWidth, mHeight); 193 | /** 194 | * 把图片缩放成我们想要的大小 195 | */ 196 | source = Bitmap.createScaledBitmap(source, mWidth, mHeight, false); 197 | /**在绘制矩形区域绘制用画笔绘制一个圆角矩形**/ 198 | canvas.drawRoundRect(rect, mRadius, mRadius, paint); 199 | /** 200 | * 我简单理解为设置画笔在绘制时图形堆叠时候的显示模式 201 | * SRC_IN:取两层绘制交集。显示上层。 202 | */ 203 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 204 | canvas.drawBitmap(source, 0, 0, paint); 205 | /****/ 206 | return target; 207 | } 208 | 209 | protected int sp2px(float spValue) { 210 | final float fontScale = mContext.getResources().getDisplayMetrics().scaledDensity; 211 | return (int) (spValue * fontScale + 0.5f); 212 | } 213 | 214 | protected int dp2px(float dp) { 215 | final float scale = mContext.getResources().getDisplayMetrics().density; 216 | return (int) (dp * scale + 0.5f); 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/simple/DefaultUseInLocalActivity.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.simple; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.view.View; 7 | import android.widget.AbsListView; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | import android.widget.Toast; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.appcompat.app.AppCompatActivity; 15 | 16 | import com.google.gson.Gson; 17 | import com.jidcoo.android.widget.commentview.CommentView; 18 | import com.jidcoo.android.widget.commentview.callback.OnCommentLoadMoreCallback; 19 | import com.jidcoo.android.widget.commentview.callback.OnItemClickCallback; 20 | import com.jidcoo.android.widget.commentview.callback.OnPullRefreshCallback; 21 | import com.jidcoo.android.widget.commentview.callback.OnReplyLoadMoreCallback; 22 | import com.jidcoo.android.widget.commentview.callback.OnScrollCallback; 23 | import com.jidcoo.android.widget.commentview.defaults.DefaultCommentModel; 24 | import com.jidcoo.android.widget.commentview.defaults.DefaultViewStyleConfigurator; 25 | import com.jidcoo.android.widgettest.R; 26 | 27 | /** 28 | * 对于CommentView的默认数据模型的简单使用(使用本地测试数据) 29 | * 使用默认样式配置器,默认数据模型,默认布局 30 | * 31 | * @author Jidcoo 32 | */ 33 | public class DefaultUseInLocalActivity extends AppCompatActivity { 34 | private CommentView commentView; 35 | private Gson gson; 36 | private EditText user, editor; 37 | private Button button; 38 | private boolean isReply = false; 39 | private boolean isChildReply = false; 40 | private int cp, rp; 41 | private long fid, pid; 42 | //本地测试数据 43 | private LocalServer localServer; 44 | private Handler handler = new Handler() { 45 | @Override 46 | public void handleMessage(@NonNull Message msg) { 47 | switch (msg.what) { 48 | case 1: 49 | //commentView.loadFailed(true);//实际网络请求中如果加载失败调用此方法 50 | commentView.loadComplete(gson.fromJson((String) msg.obj, DefaultCommentModel.class)); 51 | break; 52 | case 2: 53 | //commentView.refreshFailed();//实际网络请求中如果加载失败调用此方法 54 | commentView.refreshComplete(gson.fromJson((String) msg.obj, DefaultCommentModel.class)); 55 | break; 56 | case 3: 57 | //commentView.loadFailed();//实际网络请求中如果加载失败调用此方法 58 | commentView.loadMoreComplete(gson.fromJson((String) msg.obj, DefaultCommentModel.class)); 59 | break; 60 | case 4: 61 | //commentView.loadMoreReplyFailed();//实际网络请求中如果加载失败调用此方法 62 | commentView.loadMoreReplyComplete(gson.fromJson((String) msg.obj, DefaultCommentModel.class)); 63 | break; 64 | } 65 | 66 | } 67 | }; 68 | 69 | @Override 70 | protected void onCreate(@Nullable Bundle savedInstanceState) { 71 | super.onCreate(savedInstanceState); 72 | setContentView(R.layout.simple_use); 73 | setTitle("WidgetTest(默认样式)"); 74 | gson = new Gson(); 75 | localServer = new LocalServer(this, "api1"); 76 | user = findViewById(R.id.user); 77 | editor = findViewById(R.id.editor); 78 | button = findViewById(R.id.button); 79 | commentView = findViewById(R.id.myCommentView);//初始化控件 80 | commentView.setViewStyleConfigurator(new DefaultViewStyleConfigurator(this)); 81 | //设置空视图 82 | //commentView.setEmptyView(view); 83 | //设置错误视图 84 | //commentView.setErrorView(view); 85 | //获取callbackBuilder添加事件回调 86 | commentView.callbackBuilder() 87 | //下拉刷新回调 88 | .setOnPullRefreshCallback(new MyOnPullRefreshCallback()) 89 | //上拉加载更多回调(加载更多评论数据) 90 | .setOnCommentLoadMoreCallback(new MyOnCommentLoadMoreCallback()) 91 | //回复数据加载更多回调(加载更多回复) 92 | .setOnReplyLoadMoreCallback(new MyOnReplyLoadMoreCallback()) 93 | //评论、回复Item的点击回调(点击事件回调) 94 | .setOnItemClickCallback(new MyOnItemClickCallback()) 95 | //滚动事件回调 96 | .setOnScrollCallback(new MyOnScrollCallback()) 97 | //设置完成后必须调用CallbackBuilder的buildCallback()方法,否则设置的回调无效 98 | .buildCallback(); 99 | button.setOnClickListener(new View.OnClickListener() { 100 | @Override 101 | public void onClick(View v) { 102 | String userStr = user.getText().toString(); 103 | String data = editor.getText().toString(); 104 | if (!userStr.isEmpty() && !data.isEmpty()) { 105 | if (isReply && isChildReply) { 106 | //现在需要构建一个回复数据实体类 107 | DefaultCommentModel.Comment.Reply reply = new DefaultCommentModel.Comment.Reply(); 108 | reply.setKid(fid); 109 | reply.setReplierName(userStr); 110 | reply.setReply(data); 111 | reply.setDate(System.currentTimeMillis()); 112 | reply.setPid(pid); 113 | commentView.addReply(reply, cp); 114 | } else if (isReply && !isChildReply) { 115 | //现在需要构建一个回复数据实体类 116 | DefaultCommentModel.Comment.Reply reply = new DefaultCommentModel.Comment.Reply(); 117 | reply.setKid(fid); 118 | reply.setReplierName(userStr); 119 | reply.setReply(data); 120 | reply.setDate(System.currentTimeMillis()); 121 | reply.setPid(0); 122 | commentView.addReply(reply, cp); 123 | } else { 124 | DefaultCommentModel.Comment comment = new DefaultCommentModel.Comment(); 125 | comment.setFid(System.currentTimeMillis()); 126 | comment.setId(comment.getFid() + 1); 127 | comment.setDate(comment.getFid()); 128 | comment.setPid(0); 129 | comment.setPosterName(userStr); 130 | comment.setComment(data); 131 | commentView.addComment(comment); 132 | } 133 | } else { 134 | Toast.makeText(DefaultUseInLocalActivity.this, "用户名和内容都不能为空", Toast.LENGTH_LONG).show(); 135 | } 136 | } 137 | }); 138 | load(1, 1); 139 | } 140 | 141 | 142 | private void load(int code, int handlerId) { 143 | localServer.get(code, handler, handlerId); 144 | } 145 | 146 | 147 | /** 148 | * 下拉刷新回调类 149 | */ 150 | class MyOnPullRefreshCallback implements OnPullRefreshCallback { 151 | 152 | @Override 153 | public void refreshing() { 154 | load(1, 2); 155 | 156 | 157 | } 158 | 159 | @Override 160 | public void complete() { 161 | //加载完成后的操作 162 | } 163 | 164 | @Override 165 | public void failure(String msg) { 166 | if (msg != null) 167 | Toast.makeText(DefaultUseInLocalActivity.this, msg, Toast.LENGTH_LONG).show(); 168 | } 169 | } 170 | 171 | 172 | /** 173 | * 上拉加载更多回调类 174 | */ 175 | class MyOnCommentLoadMoreCallback implements OnCommentLoadMoreCallback { 176 | 177 | @Override 178 | public void loading(int currentPage, int willLoadPage, boolean isLoadedAllPages) { 179 | //因为测试数据写死了,所以这里的逻辑也是写死的 180 | if (!isLoadedAllPages) { 181 | if (willLoadPage == 2) { 182 | load(2, 3); 183 | } else if (willLoadPage == 3) { 184 | load(3, 3); 185 | } 186 | } 187 | } 188 | 189 | @Override 190 | public void complete() { 191 | //加载完成后的操作 192 | } 193 | 194 | @Override 195 | public void failure(String msg) { 196 | if (msg != null) 197 | Toast.makeText(DefaultUseInLocalActivity.this, msg, Toast.LENGTH_LONG).show(); 198 | } 199 | } 200 | 201 | /** 202 | * 回复加载更多回调类 203 | */ 204 | class MyOnReplyLoadMoreCallback implements OnReplyLoadMoreCallback { 205 | 206 | @Override 207 | public void loading(DefaultCommentModel.Comment.Reply reply, int willLoadPage) { 208 | //因为测试数据写死了,所以这里的逻辑也是写死的 209 | //在默认回复数据模型中,kid作为父级索引 210 | //为了扩展性,把对应的具体模型传了出来,可根据具体需求具体使用 211 | if (reply.getKid() == 1593699394031L) { 212 | load(4, 4); 213 | } else { 214 | if (willLoadPage == 2) { 215 | load(5, 4); 216 | } else if (willLoadPage == 3) { 217 | load(6, 4); 218 | } 219 | } 220 | } 221 | 222 | @Override 223 | public void complete() { 224 | //加载完成后的操作 225 | } 226 | 227 | @Override 228 | public void failure(String msg) { 229 | if (msg != null) 230 | Toast.makeText(DefaultUseInLocalActivity.this, msg, Toast.LENGTH_LONG).show(); 231 | } 232 | } 233 | 234 | /** 235 | * 点击事件回调 236 | */ 237 | class MyOnItemClickCallback implements OnItemClickCallback { 238 | 239 | @Override 240 | public void commentItemOnClick(int position, DefaultCommentModel.Comment comment, View view) { 241 | isReply = true; 242 | cp = position; 243 | isChildReply = false; 244 | fid = comment.getFid(); 245 | editor.setHint("回复@" + comment.getPosterName() + ":"); 246 | } 247 | 248 | @Override 249 | public void replyItemOnClick(int c_position, int r_position, DefaultCommentModel.Comment.Reply reply, View view) { 250 | isReply = true; 251 | cp = c_position; 252 | rp = r_position; 253 | isChildReply = true; 254 | fid = reply.getKid(); 255 | pid = reply.getId(); 256 | editor.setHint("回复@" + reply.getReplierName() + ":"); 257 | } 258 | } 259 | 260 | class MyOnScrollCallback implements OnScrollCallback { 261 | 262 | @Override 263 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 264 | isReply = false; 265 | // if (editor.getText().toString().length()>0){ 266 | // editor.setText(""); 267 | // } 268 | // editor.setHint("发表你的评论吧~"); 269 | } 270 | 271 | @Override 272 | public void onScrollStateChanged(AbsListView view, int scrollState) { 273 | 274 | } 275 | 276 | @Override 277 | public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { 278 | 279 | 280 | } 281 | } 282 | 283 | } 284 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/simple/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.simple; 2 | 3 | import android.os.Handler; 4 | import android.os.Message; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.HttpURLConnection; 9 | import java.net.MalformedURLException; 10 | import java.net.ProtocolException; 11 | import java.net.URL; 12 | import java.util.concurrent.ExecutorService; 13 | import java.util.concurrent.Executors; 14 | 15 | public class HttpServer { 16 | public static final String IP = "http://192.168.1.9:8080/CommentService"; 17 | public static ExecutorService threadPool = Executors.newSingleThreadExecutor(); 18 | 19 | public static void getData(final String url, final Handler handler, final int code) { 20 | threadPool.execute(new Runnable() { 21 | @Override 22 | public void run() { 23 | String data = get(url); 24 | Message msg = Message.obtain(); 25 | msg.what = code; 26 | msg.obj = data; 27 | handler.sendMessage(msg); 28 | } 29 | }); 30 | } 31 | 32 | public static String get(String urlStr) { 33 | //get的方式提交就是url拼接的方式 34 | HttpURLConnection connection = null; 35 | try { 36 | URL url = new URL(urlStr); 37 | connection = (HttpURLConnection) url.openConnection(); 38 | connection.setConnectTimeout(5000); 39 | connection.setRequestMethod("GET"); 40 | //获得结果码 41 | int responseCode = connection.getResponseCode(); 42 | if (responseCode == 200) { 43 | //请求成功 获得返回的流 44 | InputStream is = connection.getInputStream(); 45 | return inputStream2String(is); 46 | } else { 47 | //请求失败 48 | return null; 49 | } 50 | } catch (MalformedURLException e) { 51 | e.printStackTrace(); 52 | } catch (ProtocolException e) { 53 | e.printStackTrace(); 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | } finally { 57 | connection.disconnect(); 58 | } 59 | return null; 60 | } 61 | 62 | 63 | public static String inputStream2String(InputStream in) throws IOException { 64 | StringBuffer out = new StringBuffer(); 65 | byte[] b = new byte[4096]; 66 | 67 | for (int n; (n = in.read(b)) != -1; ) { 68 | out.append(new String(b, 0, n)); 69 | } 70 | in.close(); 71 | return out.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/jidcoo/android/widgettest/simple/LocalServer.java: -------------------------------------------------------------------------------- 1 | package com.jidcoo.android.widgettest.simple; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | import static com.jidcoo.android.widgettest.simple.HttpServer.threadPool; 11 | 12 | /** 13 | * 注意这里读取文件是在主线程的。 14 | */ 15 | public class LocalServer { 16 | private Context context; 17 | private String api; 18 | private volatile boolean isFirstLoad = true; 19 | 20 | public LocalServer(Context context, String api) { 21 | this.context = context; 22 | this.api = api; 23 | } 24 | 25 | public String get(int code, Handler handler, int handlerId) { 26 | switch (code) { 27 | case 1: 28 | //首次加载和刷新 29 | return readFile(api + "/p1.json", handler, handlerId); 30 | case 2: 31 | //加载第二页 32 | return readFile(api + "/p2.json", handler, handlerId); 33 | case 3: 34 | //加载第三页 35 | return readFile(api + "/p3.json", handler, handlerId); 36 | case 4: 37 | //加载第一页的更多回复 38 | return readFile(api + "/p1_r1.json", handler, handlerId); 39 | case 5: 40 | //加载第三页的更多回复(第二页) 41 | return readFile(api + "/p3_r1.json", handler, handlerId); 42 | case 6: 43 | //加载第三页的更多回复(第二页) 44 | return readFile(api + "/p3_r2.json", handler, handlerId); 45 | } 46 | return null; 47 | } 48 | 49 | 50 | private String readFile(final String path, final Handler handler, final int handlerId) { 51 | threadPool.execute(new Runnable() { 52 | @Override 53 | public void run() { 54 | InputStream is = null; 55 | try { 56 | is = context.getAssets().open(path); 57 | String rs = HttpServer.inputStream2String(is); 58 | if (!isFirstLoad) { 59 | Thread.sleep(1500);//模拟网络延迟 60 | } else { 61 | isFirstLoad = false; 62 | } 63 | Message message = Message.obtain(); 64 | message.what = handlerId; 65 | message.obj = rs; 66 | handler.sendMessage(message); 67 | } catch (IOException e) { 68 | e.printStackTrace(); 69 | } catch (InterruptedException e) { 70 | e.printStackTrace(); 71 | } 72 | } 73 | }); 74 | 75 | 76 | return ""; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/round_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 |