├── .gitignore ├── .idea ├── markdown-exported-files.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── kotlin │ │ └── scrollingtable │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── kotlin │ │ │ └── scrollingtable │ │ │ ├── MainActivity.kt │ │ │ ├── type1 │ │ │ ├── RvType1LeftAdapter.kt │ │ │ ├── RvType1RightAdapter.kt │ │ │ ├── Type1Activity.kt │ │ │ └── model │ │ │ │ ├── Type1PriceModel.kt │ │ │ │ └── Type1ProductModel.kt │ │ │ ├── type2 │ │ │ ├── RvType2Adapter.kt │ │ │ ├── RvType2RightAdapter.kt │ │ │ ├── Type2Activity.kt │ │ │ └── model │ │ │ │ └── Type2Model.kt │ │ │ ├── type3 │ │ │ ├── RvType3LeftAdapter.kt │ │ │ ├── RvType3RightAdapter.kt │ │ │ ├── Type3Activity.kt │ │ │ └── model │ │ │ │ ├── Type3PriceModel.kt │ │ │ │ └── Type3ProductModel.kt │ │ │ ├── type4 │ │ │ ├── Type4Activity.kt │ │ │ ├── Type4Adapter.kt │ │ │ ├── model │ │ │ │ └── ProductData.kt │ │ │ └── view │ │ │ │ ├── InterceptScrollContainer.kt │ │ │ │ └── SyncHScrollView.kt │ │ │ ├── type5 │ │ │ ├── RvType5Adapter.kt │ │ │ ├── Type5Activity.kt │ │ │ └── view │ │ │ │ ├── InterceptScrollLinerLayout.kt │ │ │ │ ├── SyncHScrollView.java │ │ │ │ └── SyncRecyclerView.java │ │ │ └── type6 │ │ │ ├── Type6Activity.kt │ │ │ └── Type6Adapter.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── selector_bg_blue.xml │ │ └── selector_bg_white.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_type1.xml │ │ ├── activity_type2.xml │ │ ├── activity_type3.xml │ │ ├── activity_type4.xml │ │ ├── activity_type5.xml │ │ ├── activity_type6.xml │ │ ├── item_layout.xml │ │ ├── item_layout_type1.xml │ │ ├── item_layout_type2.xml │ │ ├── item_layout_type3.xml │ │ ├── item_layout_type4.xml │ │ ├── item_layout_type5.xml │ │ ├── item_layout_type5_head.xml │ │ ├── item_layout_type6.xml │ │ └── item_layout_type6_group.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── kotlin │ └── scrollingtable │ └── ExampleUnitTest.kt ├── build.gradle ├── doc ├── Gif_20180201_224640.gif ├── Gif_20180201_224941.gif ├── Gif_20180201_225941.gif ├── Gif_20180202_001417.gif ├── Gif_20180202_001640.gif ├── device-2018-01-31-235241.mp4 ├── 下载链接二维码.png ├── 深度截图_选择区域_20180201223904.png └── 视频转GIF2.0版.zip ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | -------------------------------------------------------------------------------- /.idea/markdown-exported-files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [donghongyu] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScrollingTable 2 | 3 | > 说明:本项目使用了多种实现方式,根据不同的业务需求去选定; 4 | > 5 | > Type1(使用两个列表):(左侧)RecycleView + (右侧)【HorizontalScrollView + RecycleView(使用GridLayoutManager)】 6 | > 7 | > Type2(使用一个列表):RecycleView + Item布局{(左边)TextView+(右边)RecycleView} 【沒有完成列表中联动】 8 | > 9 | > Type5(使用两个列表):(左侧)RecycleView + (右侧)【HorizontalScrollView + RecycleView(使用LinearLayoutManager)】 10 | > 11 | > Type4(本项目的最佳实现【推荐】):ListView + Item布局{(左边)TextView+(右边)HorizontalScrollView} 12 | 13 | ## [下载APK](http://fir.im/4e96) 14 | ![](/doc/下载链接二维码.png) 15 | 16 | > 要求:可以上下左右移动的表格布局,仿同花顺自选列表,老虎证券财报列表 17 | 18 | | 同花顺效果 | 老虎证券效果| 最终实现效果如图| 19 | | -------- | -----| -----| 20 | | | | | 21 | 22 | 23 | ## 实现思路分析 24 | 25 | 26 | ## 视图分析 27 | 1、**主视图分为: 头部控件(HeadView)+下面的ListView** 28 | 29 | 2、**头部控件(HeadView):左边为 TextView,右边为 HorizontalScrollView** 30 | 31 | 3、**ListView 条目视图:左边为 TextView,右边为 HorizontalScrollView** 32 | 33 | ## 视图联动分析 34 | 1、**头部 HorizontalScrollView 滑动事件广播通知 ListView 条目中的 HorizontalScrollView 从而实现联动效果** 35 | 36 | 2、**拦截 ListView 单个条目中的 HorizontalScrollView 滑动事件,防止 ListView 的触摸事件和 HorizontalScrollView 触摸事件冲突** 37 | 38 | 3、**统一处理 ListView 和 头部控件(HeadView)触摸事件,统一将触摸事件传递给 头部控件(HeadView)右边的 HorizontalScrollView ,从而实现(1)中的效果** 39 | 40 | ````java 41 | /** 42 | * Created by xiaoyulaoshi on 2018/1/31. 43 | * 44 | * 自定义的 滚动控件 45 | * 重载了 [SyncHScrollView.onScrollChanged](滚动条变化),监听每次的变化通知给观察(此变化的)观察者 46 | * 可使用 [SyncHScrollView.AddOnScrollChangedListener] 来订阅本控件的 滚动条变化 47 | */ 48 | class SyncHScrollView : HorizontalScrollView { 49 | internal var mScrollViewObserver: ScrollViewObserver? = ScrollViewObserver() 50 | 51 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {} 52 | 53 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 54 | 55 | constructor(context: Context) : super(context) {} 56 | 57 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 58 | /* 59 | * 当滚动条移动后,引发 滚动事件。通知给观察者,观察者会传达给其他的条目中的滚动视图。 60 | */ 61 | if (mScrollViewObserver != null) { 62 | mScrollViewObserver!!.NotifyOnScrollChanged(l, t, oldl, oldt) 63 | } 64 | super.onScrollChanged(l, t, oldl, oldt) 65 | } 66 | 67 | /* 68 | * 订阅 本控件 的 滚动条变化事件 69 | * */ 70 | fun AddOnScrollChangedListener(listener: OnScrollChangedListener) { 71 | mScrollViewObserver!!.AddOnScrollChangedListener(listener) 72 | } 73 | 74 | /* 75 | * 取消 订阅 本控件 的 滚动条变化事件 76 | * */ 77 | fun RemoveOnScrollChangedListener(listener: OnScrollChangedListener) { 78 | mScrollViewObserver!!.RemoveOnScrollChangedListener(listener) 79 | } 80 | 81 | /* 82 | * 当发生了滚动事件时 83 | */ 84 | interface OnScrollChangedListener { 85 | fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) 86 | } 87 | 88 | /** 89 | * 观察者 90 | */ 91 | class ScrollViewObserver { 92 | internal var mList: MutableList? = null 93 | 94 | init { 95 | mList = ArrayList() 96 | } 97 | 98 | fun AddOnScrollChangedListener(listener: OnScrollChangedListener) { 99 | mList!!.add(listener) 100 | } 101 | 102 | fun RemoveOnScrollChangedListener( 103 | listener: OnScrollChangedListener) { 104 | mList!!.remove(listener) 105 | } 106 | 107 | fun NotifyOnScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 108 | if (mList == null || mList!!.size == 0) { 109 | return 110 | } 111 | for (i in mList!!.indices) { 112 | if (mList!![i] != null) { 113 | mList!![i].onScrollChanged(l, t, oldl, oldt) 114 | } 115 | } 116 | } 117 | } 118 | } 119 | ```` 120 | 121 | ````java 122 | /** 123 | * ListView使用的数据适配器,实现数据填充以及列表右侧的 HorizontalScrollView 与 页面中的 头部控件(HeadView) 右侧 HorizontalScrollView 的绑定 124 | * 125 | * Created by xiaoyulaoshi on 2018/1/31. 126 | */ 127 | class Type4Adapter(context: Context, 128 | /** 129 | * layout ID 130 | */ 131 | private val id_row_layout: Int, 132 | /** 133 | * List中的数据 134 | */ 135 | private val currentData: MutableList, 136 | /** 137 | * ListView头部 138 | */ 139 | private val mHead: RelativeLayout) : BaseAdapter() { 140 | private val mInflater: LayoutInflater 141 | 142 | 143 | init { 144 | Log.v(TAG + ".Type4Adapter", " 初始化") 145 | this.mInflater = LayoutInflater.from(context) 146 | 147 | } 148 | 149 | override fun getCount(): Int { 150 | return this.currentData.size 151 | } 152 | 153 | override fun getItem(position: Int): Any? { 154 | return null 155 | } 156 | 157 | override fun getItemId(position: Int): Long { 158 | return 0 159 | } 160 | 161 | /** 162 | * 向List中添加数据 163 | * 164 | * @param items 165 | */ 166 | fun addItem(items: List) { 167 | for (item in items) { 168 | currentData.add(item) 169 | } 170 | } 171 | 172 | /** 173 | * 清空当List中的数据 174 | */ 175 | fun cleanAll() { 176 | this.currentData.clear() 177 | } 178 | 179 | @SuppressLint("SetTextI18n") 180 | override fun getView(position: Int, convertView: View?, parentView: ViewGroup): View { 181 | var convertView = convertView 182 | var holder: ViewHolder? = null 183 | if (convertView == null) { 184 | convertView = mInflater.inflate(id_row_layout, null) 185 | holder = ViewHolder() 186 | 187 | //获取当前条目中的右侧滑动控件 188 | val scrollView1 = convertView!!.findViewById(R.id.horizontalScrollView1) 189 | 190 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 191 | val headScrollView = mHead.findViewById(R.id.horizontalScrollView1) 192 | //将当前条目的右侧滑动控件添加到头部滑动控件的滑动观察者集合中 193 | headScrollView.AddOnScrollChangedListener(OnScrollChangedListenerImp(scrollView1)) 194 | 195 | 196 | //进行holder的初始化操作 197 | holder.scrollView = scrollView1 198 | holder.txt1 = convertView.findViewById(R.id.textView1) 199 | holder.txt2 = convertView.findViewById(R.id.textView2) 200 | holder.txt3 = convertView.findViewById(R.id.textView3) 201 | holder.txt4 = convertView.findViewById(R.id.textView4) 202 | holder.txt5 = convertView.findViewById(R.id.textView5) 203 | holder.txt6 = convertView.findViewById(R.id.textView6) 204 | holder.txt7 = convertView.findViewById(R.id.textView7) 205 | 206 | convertView.tag = holder 207 | } else { 208 | holder = convertView.tag as ViewHolder 209 | } 210 | holder.txt1!!.text = currentData[position].str1 211 | holder.txt2!!.text = currentData[position].str1!! + currentData[position].str2!! 212 | holder.txt3!!.text = currentData[position].str1!! + currentData[position].str3!! 213 | holder.txt4!!.text = currentData[position].str1!! + currentData[position].str4!! 214 | holder.txt5!!.text = currentData[position].str1!! + currentData[position].str5!! 215 | holder.txt6!!.text = currentData[position].str1!! + currentData[position].str6!! 216 | holder.txt7!!.text = currentData[position].str1!! + currentData[position].str7!! 217 | return convertView 218 | } 219 | 220 | internal inner class OnScrollChangedListenerImp(var mScrollViewArg: SyncHScrollView) : 221 | SyncHScrollView.OnScrollChangedListener { 222 | 223 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 224 | mScrollViewArg.smoothScrollTo(l, t) 225 | } 226 | } 227 | 228 | internal inner class ViewHolder { 229 | var txt1: TextView? = null 230 | var txt2: TextView? = null 231 | var txt3: TextView? = null 232 | var txt4: TextView? = null 233 | var txt5: TextView? = null 234 | var txt6: TextView? = null 235 | var txt7: TextView? = null 236 | var scrollView: HorizontalScrollView? = null 237 | } 238 | 239 | companion object { 240 | private val TAG = Type4Adapter::class.java.name 241 | } 242 | } 243 | ```` 244 | 245 | 246 | ````java 247 | 248 | /** 249 | * 最完美实现,使用 ListView + HorizontalScrollView 实现 250 | * Created by xiaoyulaoshi on 2018/1/31. 251 | */ 252 | class Type4Activity : Activity() { 253 | internal lateinit var mListView1: ListView 254 | internal lateinit var mHead: RelativeLayout 255 | internal lateinit var type4Adapter: Type4Adapter 256 | 257 | public override fun onCreate(savedInstanceState: Bundle?) { 258 | super.onCreate(savedInstanceState) 259 | setContentView(R.layout.activity_type4) 260 | 261 | mHead = findViewById(R.id.head) 262 | mHead.isFocusable = true 263 | mHead.isClickable = true 264 | 265 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 266 | mHead.setOnTouchListener(ListViewAndHeadViewTouchListener()) 267 | 268 | 269 | mListView1 = findViewById(R.id.lv_produce) 270 | mListView1.setOnTouchListener(ListViewAndHeadViewTouchListener()) 271 | 272 | // 创建当前用于显示视图的数据 273 | val currentData = ArrayList() 274 | for (i in 0..49) { 275 | val data = Data() 276 | data.str1 = "股票>" + i 277 | data.str2 = "价格>1" 278 | data.str3 = "价格>2" 279 | data.str4 = "价格>3" 280 | data.str5 = "价格>4" 281 | data.str6 = "价格>5" 282 | data.str7 = "价格>6" 283 | data.str8 = "价格>7" 284 | currentData.add(data) 285 | } 286 | 287 | 288 | type4Adapter = Type4Adapter(this, R.layout.item_layout_type4, currentData, mHead) 289 | mListView1.adapter = type4Adapter 290 | // OnClick监听 291 | mListView1.onItemClickListener = OnItemClickListener { arg0, arg1, arg2, arg3 -> 292 | Log.i("Type4Activity ListView", "onItemClick Event") 293 | Toast.makeText(this@Type4Activity, "点了第" + arg2 + "个", 294 | Toast.LENGTH_SHORT).show() 295 | } 296 | 297 | } 298 | 299 | /** 300 | * TODO 划重点:用来将头部和列表上面的触摸事件都分发给头部的滑动控件 301 | */ 302 | internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener { 303 | 304 | override fun onTouch(arg0: View, arg1: MotionEvent): Boolean { 305 | // 当在列头 和 listView控件上touch时,将这个touch的事件分发给 ScrollView 306 | val headScrollView = mHead.findViewById(R.id.horizontalScrollView1) 307 | headScrollView.onTouchEvent(arg1) 308 | return false 309 | } 310 | } 311 | 312 | } 313 | ```` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 26 9 | defaultConfig { 10 | applicationId "com.example.kotlin.scrollingtable" 11 | minSdkVersion 15 12 | targetSdkVersion 26 13 | versionCode 2 14 | versionName "1.2" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 28 | implementation 'com.android.support:appcompat-v7:26.1.0' 29 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 30 | 31 | implementation 'com.android.support:design:26.1.0' 32 | implementation 'com.android.support:recyclerview-v7:26.1.0' 33 | implementation 'com.android.support:cardview-v7:26.1.0' 34 | implementation 'com.android.support:gridlayout-v7:26.1.0' 35 | 36 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.34' 37 | 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 40 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 41 | } 42 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/kotlin/scrollingtable/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.example.kotlin.scrollingtable", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 32 | 33 | 34 | 39 | 40 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v7.app.AppCompatActivity 6 | import android.view.View 7 | import com.example.kotlin.scrollingtable.type1.Type1Activity 8 | import com.example.kotlin.scrollingtable.type2.Type2Activity 9 | import com.example.kotlin.scrollingtable.type3.Type3Activity 10 | import com.example.kotlin.scrollingtable.type4.Type4Activity 11 | import com.example.kotlin.scrollingtable.type5.Type5Activity 12 | import com.example.kotlin.scrollingtable.type6.Type6Activity 13 | 14 | /** 15 | * 主入口 16 | * Created by xiaoyulaoshi on 2018/1/31. 17 | */ 18 | 19 | class MainActivity : AppCompatActivity() { 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_main) 24 | } 25 | 26 | 27 | fun jumpType1(view: View) { 28 | val intent = Intent(this, Type1Activity::class.java) 29 | startActivity(intent) 30 | } 31 | 32 | fun jumpType2(view: View) { 33 | val intent = Intent(this, Type2Activity::class.java) 34 | startActivity(intent) 35 | } 36 | 37 | fun jumpType3(view: View) { 38 | val intent = Intent(this, Type3Activity::class.java) 39 | startActivity(intent) 40 | } 41 | 42 | fun jumpType4(view: View) { 43 | val intent = Intent(this, Type4Activity::class.java) 44 | startActivity(intent) 45 | } 46 | 47 | fun jumpType5(view: View) { 48 | val intent = Intent(this, Type5Activity::class.java) 49 | startActivity(intent) 50 | } 51 | 52 | fun jumpType6(view: View) { 53 | val intent = Intent(this, Type6Activity::class.java) 54 | startActivity(intent) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type1/RvType1LeftAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type1 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.BaseViewHolder 5 | import com.example.kotlin.scrollingtable.R 6 | import com.example.kotlin.scrollingtable.type1.model.Type1ProductModel 7 | 8 | /** 9 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 10 | * Created by kotlin on 18-1-29. 11 | */ 12 | class RvType1LeftAdapter : BaseQuickAdapter(R.layout.item_layout_type1) { 13 | private var TAG = RvType1LeftAdapter::class.java.name 14 | 15 | var rvMainRightAdapter: RvType1RightAdapter? = null 16 | 17 | 18 | override fun convert(helper: BaseViewHolder, item: Type1ProductModel) { 19 | //当前的条目位置信息 20 | val productPosition = helper.adapterPosition 21 | 22 | helper.setText(R.id.tv_data, item.productName) 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type1/RvType1RightAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type1 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.BaseViewHolder 5 | import com.example.kotlin.scrollingtable.R 6 | import com.example.kotlin.scrollingtable.type1.model.Type1PriceModel 7 | 8 | /** 9 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 10 | * Created by kotlin on 18-1-29. 11 | */ 12 | class RvType1RightAdapter : BaseQuickAdapter(R.layout.item_layout_type1) { 13 | private var TAG = RvType1RightAdapter::class.java.name 14 | 15 | var rvRvMainLeftAdapter: RvType1LeftAdapter? = null 16 | 17 | override fun convert(helper: BaseViewHolder, item: Type1PriceModel) { 18 | //当前的条目位置信息 19 | val productPosition = helper.adapterPosition 20 | 21 | helper.setText(R.id.tv_data, item.priceName) 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type1/Type1Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type1 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import android.util.Log 7 | import com.example.kotlin.scrollingtable.R 8 | import com.example.kotlin.scrollingtable.type1.model.Type1PriceModel 9 | import com.example.kotlin.scrollingtable.type1.model.Type1ProductModel 10 | import kotlinx.android.synthetic.main.activity_type1.* 11 | 12 | class Type1Activity : AppCompatActivity() { 13 | private var TAG = Type1Activity::class.java.name 14 | 15 | private var mLeftAdapter: RvType1LeftAdapter? = null 16 | private var mLeftDataList: MutableList = mutableListOf() 17 | 18 | private var mRightAdapter: RvType1RightAdapter? = null 19 | private var mRightDataList: MutableList = mutableListOf() 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_type1) 24 | 25 | mLeftAdapter = RvType1LeftAdapter() 26 | mRightAdapter = RvType1RightAdapter() 27 | 28 | 29 | for (produceIndex in 0..40) { 30 | val productModel = Type1ProductModel() 31 | productModel.producrId = produceIndex 32 | productModel.productName = "股票名称$produceIndex" 33 | 34 | for (priceIndex in 0..4) { 35 | val mainPriceModel = Type1PriceModel() 36 | mainPriceModel.priceName = "股票${produceIndex}价格-${priceIndex}" 37 | mainPriceModel.productModel = productModel 38 | mRightDataList.add(mainPriceModel) 39 | } 40 | 41 | mLeftDataList.add(productModel) 42 | } 43 | 44 | 45 | mLeftAdapter!!.setNewData(mLeftDataList) 46 | rv_list_left.adapter = mLeftAdapter 47 | 48 | 49 | mRightAdapter!!.setNewData(mRightDataList) 50 | rv_list_right.adapter = mRightAdapter 51 | 52 | mLeftAdapter!!.rvMainRightAdapter = mRightAdapter 53 | mRightAdapter!!.rvRvMainLeftAdapter = mLeftAdapter 54 | 55 | //注册条目点击监听 56 | mLeftAdapter?.setOnItemClickListener { adapter, view, position -> 57 | Log.d(TAG, "mLeftAdapter click position >> " + position) 58 | } 59 | mRightAdapter?.setOnItemClickListener { adapter, view, position -> 60 | Log.d(TAG, "mRightAdapter click position >> " + position) 61 | } 62 | 63 | 64 | //TODO 注意喽,要划重点了,要考的 65 | //TODO 左侧的RecyclerView与右侧RecyclerView垂直方向的滑动相互监听,实现联动效果 66 | rv_list_left.addOnScrollListener(object : RecyclerView.OnScrollListener() { 67 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 68 | if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) { 69 | rv_list_right.scrollBy(dx, dy) 70 | } 71 | } 72 | }) 73 | rv_list_right.addOnScrollListener(object : RecyclerView.OnScrollListener() { 74 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 75 | if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) { 76 | rv_list_left.scrollBy(dx, dy) 77 | } 78 | } 79 | }) 80 | 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type1/model/Type1PriceModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type1.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/30. 5 | */ 6 | class Type1PriceModel { 7 | //当前条目是否选中 8 | var isPressed = false 9 | 10 | //产品名称 11 | var priceName: String? = null 12 | 13 | //当前产品价格属于哪个产品 14 | var productModel: Type1ProductModel? = null 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type1/model/Type1ProductModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type1.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/30. 5 | */ 6 | class Type1ProductModel { 7 | 8 | var producrId: Int = 0 9 | //当前条目是否选中 10 | var isPressed = false 11 | //产品名称 12 | var productName: String? = null 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type2/RvType2Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type2 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.MotionEvent 5 | import com.chad.library.adapter.base.BaseQuickAdapter 6 | import com.chad.library.adapter.base.BaseViewHolder 7 | import com.example.kotlin.scrollingtable.R 8 | import com.example.kotlin.scrollingtable.type2.model.Type2Model 9 | 10 | /** 11 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 12 | * Created by kotlin on 18-1-29. 13 | */ 14 | class RvType2Adapter : BaseQuickAdapter(R.layout.item_layout_type2) { 15 | private var TAG = RvType2Adapter::class.java.name 16 | 17 | 18 | override fun convert(helper: BaseViewHolder, item: Type2Model) { 19 | //当前的条目位置信息 20 | val productPosition = helper.adapterPosition 21 | 22 | helper.setText(R.id.tv_product_name, item.productName) 23 | 24 | val rightAdapter = RvType2RightAdapter() 25 | rightAdapter.setNewData(item.mPriceList) 26 | val itemRecyclerView = helper.getView(R.id.rv_list_right) 27 | 28 | itemRecyclerView.adapter = rightAdapter 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type2/RvType2RightAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type2 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.BaseViewHolder 5 | import com.example.kotlin.scrollingtable.R 6 | 7 | /** 8 | * Created by kotlin on 18-1-29. 9 | */ 10 | class RvType2RightAdapter : BaseQuickAdapter(R.layout.item_layout_type1) { 11 | override fun convert(helper: BaseViewHolder, item: String) { 12 | helper.setText(R.id.tv_data, item) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type2/Type2Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type2 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.util.Log 6 | import com.example.kotlin.scrollingtable.R 7 | import com.example.kotlin.scrollingtable.type2.model.Type2Model 8 | import kotlinx.android.synthetic.main.activity_type2.* 9 | 10 | class Type2Activity : AppCompatActivity() { 11 | 12 | private var TAG = Type2Activity::class.java.name 13 | 14 | private var mProductAdapter: RvType2Adapter? = null 15 | private var mProductDataList: MutableList? = null 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_type2) 20 | 21 | mProductAdapter = RvType2Adapter() 22 | mProductDataList = mutableListOf() 23 | for (index in 0..40) { 24 | val productModel = Type2Model() 25 | productModel.productName = "股票名称${index}" 26 | val priceList: MutableList = mutableListOf() 27 | 28 | for (indexPrice in 0..5) { 29 | priceList.add("股票${index}价格${indexPrice}") 30 | } 31 | productModel.mPriceList = priceList 32 | 33 | mProductDataList!!.add(productModel) 34 | } 35 | mProductAdapter!!.setNewData(mProductDataList) 36 | rv_list_product.adapter = mProductAdapter 37 | 38 | mProductAdapter?.setOnItemClickListener { adapter, view, position -> 39 | Log.d(TAG, "position>>" + position) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type2/model/Type2Model.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type2.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/30. 5 | */ 6 | class Type2Model { 7 | //当前条目是否选中 8 | var isPressed = false 9 | //产品名称 10 | var productName: String? = null 11 | //产品价格数据集合 12 | var mPriceList: MutableList? = null 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type3/RvType3LeftAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type3 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.util.Log 5 | import android.view.MotionEvent 6 | import com.chad.library.adapter.base.BaseQuickAdapter 7 | import com.chad.library.adapter.base.BaseViewHolder 8 | import com.example.kotlin.scrollingtable.R 9 | import com.example.kotlin.scrollingtable.type3.model.Type3ProductModel 10 | 11 | /** 12 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 13 | * Created by kotlin on 18-1-29. 14 | */ 15 | class RvType3LeftAdapter : BaseQuickAdapter(R.layout.item_layout_type1) { 16 | private var TAG = RvType3LeftAdapter::class.java.name 17 | 18 | var mRightLinearLayoutManager: LinearLayoutManager? = null 19 | var mRvType3RightAdapter: RvType3RightAdapter? = null 20 | 21 | override fun convert(helper: BaseViewHolder, item: Type3ProductModel) { 22 | //当前的条目位置信息 23 | val productPosition = helper.adapterPosition 24 | 25 | helper.setText(R.id.tv_data, item.productName) 26 | 27 | helper.itemView.isPressed = item.isPressed 28 | 29 | helper.itemView.setOnTouchListener { view, event -> 30 | Log.d(TAG, "event >>>> " + event.action) 31 | 32 | when (event.action) { 33 | MotionEvent.ACTION_DOWN -> { 34 | helper.itemView.isPressed = true 35 | item.isPressed = true 36 | } 37 | MotionEvent.ACTION_UP -> { 38 | helper.itemView.isPressed = false 39 | item.isPressed = false 40 | } 41 | MotionEvent.ACTION_CANCEL -> { 42 | helper.itemView.isPressed = false 43 | item.isPressed = false 44 | } 45 | } 46 | mRvType3RightAdapter?.notifyItemChanged(productPosition) 47 | false 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type3/RvType3RightAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type3 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.util.Log 5 | import android.view.MotionEvent 6 | import android.widget.LinearLayout 7 | import android.widget.TextView 8 | import com.chad.library.adapter.base.BaseQuickAdapter 9 | import com.chad.library.adapter.base.BaseViewHolder 10 | import com.example.kotlin.scrollingtable.R 11 | import com.example.kotlin.scrollingtable.type3.model.Type3ProductModel 12 | 13 | /** 14 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 15 | * Created by kotlin on 18-1-29. 16 | */ 17 | class RvType3RightAdapter : BaseQuickAdapter(R.layout.item_layout_type3) { 18 | private var TAG = RvType3RightAdapter::class.java.name 19 | 20 | var mLeftLinearLayoutManager: LinearLayoutManager? = null 21 | var mRvType3LeftAdapter: RvType3LeftAdapter? = null 22 | 23 | override fun convert(helper: BaseViewHolder, item: Type3ProductModel) { 24 | //当前的条目位置信息 25 | val productPosition = helper.adapterPosition 26 | 27 | val ll_item = helper.getView(R.id.ll_item) 28 | ll_item.removeAllViews() 29 | 30 | item.mRightDataList.forEach { 31 | val itemView = getItemView(R.layout.item_layout, null) 32 | itemView.findViewById(R.id.tv_data).text = it.priceName 33 | ll_item.addView(itemView) 34 | } 35 | 36 | 37 | helper.itemView.isPressed = item.isPressed 38 | 39 | 40 | helper.itemView.setOnTouchListener { view, event -> 41 | Log.d(TAG, "event >>>> " + event.action) 42 | when (event.action) { 43 | MotionEvent.ACTION_DOWN -> { 44 | helper.itemView.isPressed = true 45 | item.isPressed = true 46 | } 47 | MotionEvent.ACTION_UP -> { 48 | helper.itemView.isPressed = false 49 | item.isPressed = false 50 | } 51 | MotionEvent.ACTION_CANCEL -> { 52 | helper.itemView.isPressed = false 53 | item.isPressed = false 54 | } 55 | } 56 | mRvType3LeftAdapter?.notifyItemChanged(productPosition) 57 | false 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type3/Type3Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type3 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.widget.RecyclerView 6 | import android.util.Log 7 | import com.example.kotlin.scrollingtable.R 8 | import com.example.kotlin.scrollingtable.type3.model.Type3PriceModel 9 | import com.example.kotlin.scrollingtable.type3.model.Type3ProductModel 10 | import kotlinx.android.synthetic.main.activity_type3.* 11 | 12 | class Type3Activity : AppCompatActivity() { 13 | private var TAG = Type3Activity::class.java.name 14 | 15 | private var mLeftAdapter: RvType3LeftAdapter? = null 16 | 17 | private var mRightAdapter: RvType3RightAdapter? = null 18 | 19 | private var mDataList: MutableList = mutableListOf() 20 | 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_type3) 25 | 26 | mLeftAdapter = RvType3LeftAdapter() 27 | mRightAdapter = RvType3RightAdapter() 28 | 29 | 30 | for (produceIndex in 10..60) { 31 | val productModel = Type3ProductModel() 32 | productModel.productName = "股票名称$produceIndex" 33 | 34 | for (priceIndex in 1..4) { 35 | val mainPriceModel = Type3PriceModel() 36 | mainPriceModel.priceName = "股票${produceIndex}价格-${priceIndex}" 37 | productModel.mRightDataList.add(mainPriceModel) 38 | } 39 | 40 | mDataList.add(productModel) 41 | } 42 | 43 | 44 | mLeftAdapter!!.setNewData(mDataList) 45 | rv_list_left.adapter = mLeftAdapter 46 | 47 | mRightAdapter!!.setNewData(mDataList) 48 | rv_list_right.adapter = mRightAdapter 49 | 50 | mLeftAdapter!!.mRvType3RightAdapter = mRightAdapter 51 | mRightAdapter!!.mRvType3LeftAdapter = mLeftAdapter 52 | 53 | //注册条目点击监听 54 | mLeftAdapter?.setOnItemClickListener { adapter, view, position -> 55 | Log.d(TAG, "mLeftAdapter click position >> " + position) 56 | } 57 | mRightAdapter?.setOnItemClickListener { adapter, view, position -> 58 | Log.d(TAG, "mRightAdapter click position >> " + position) 59 | } 60 | 61 | 62 | //TODO 注意喽,要划重点了,要考的 63 | //TODO 左侧的RecyclerView与右侧RecyclerView垂直方向的滑动相互监听,实现联动效果 64 | rv_list_left.addOnScrollListener(object : RecyclerView.OnScrollListener() { 65 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 66 | if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) { 67 | rv_list_right.scrollBy(dx, dy) 68 | } 69 | } 70 | }) 71 | rv_list_right.addOnScrollListener(object : RecyclerView.OnScrollListener() { 72 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 73 | if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) { 74 | rv_list_left.scrollBy(dx, dy) 75 | } 76 | } 77 | }) 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type3/model/Type3PriceModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type3.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/30. 5 | */ 6 | class Type3PriceModel { 7 | //产品名称 8 | var priceName: String = "" 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type3/model/Type3ProductModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type3.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/30. 5 | */ 6 | class Type3ProductModel { 7 | //当前条目是否选中 8 | var isPressed = false 9 | //产品名称 10 | var productName: String = "" 11 | //产品内部的价格列表 12 | var mRightDataList: MutableList = mutableListOf() 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type4/Type4Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type4 2 | 3 | 4 | import android.app.Activity 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.widget.AbsListView 10 | import android.widget.AdapterView.OnItemClickListener 11 | import android.widget.ListView 12 | import android.widget.Toast 13 | import com.example.kotlin.scrollingtable.R 14 | import com.example.kotlin.scrollingtable.type4.model.ProductData 15 | import com.example.kotlin.scrollingtable.type4.view.SyncHScrollView 16 | import java.util.* 17 | 18 | 19 | /** 20 | * 最完美实现,使用 ListView + HorizontalScrollView 实现 21 | * Created by xiaoyulaoshi on 2018/1/31. 22 | */ 23 | class Type4Activity : Activity() { 24 | 25 | //头部吸顶视图 26 | internal lateinit var mHeadStickyView: View 27 | //头部吸顶的右侧滑动视图 28 | internal lateinit var mHeadStickyHSView: SyncHScrollView 29 | //列表的头部视图 30 | internal lateinit var mHeadHeaderView: View 31 | //列表的头部视图的右侧滑动视图 32 | internal lateinit var mHeadHeaderHSView: SyncHScrollView 33 | //列表视图 34 | internal lateinit var mListView: ListView 35 | //数据适配器 36 | internal lateinit var type4Adapter: Type4Adapter 37 | 38 | public override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | setContentView(R.layout.activity_type4) 41 | 42 | //列表的头部视图 43 | mHeadHeaderView = View.inflate(this, R.layout.item_layout_type4, null) 44 | mHeadHeaderHSView = mHeadHeaderView.findViewById(R.id.horizontalScrollView1) 45 | mHeadHeaderView.isClickable = true 46 | 47 | //头部吸顶视图 48 | mHeadStickyView = findViewById(R.id.head) 49 | mHeadStickyHSView = findViewById(R.id.horizontalScrollView1) 50 | mHeadStickyHSView.AddOnScrollChangedListener(Type4Adapter.OnScrollChangedListenerImp(mHeadHeaderHSView)) 51 | 52 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 53 | mHeadHeaderView.setOnTouchListener(ListViewAndHeadViewTouchListener()) 54 | 55 | mListView = findViewById(R.id.lv_produce) 56 | mListView.addHeaderView(mHeadHeaderView) 57 | mListView.setOnTouchListener(ListViewAndHeadViewTouchListener()) 58 | 59 | // 创建当前用于显示视图的数据 60 | val currentData = ArrayList() 61 | for (i in 0..49) { 62 | val data = ProductData() 63 | data.str1 = "股票>" + i 64 | data.str2 = "价格>1" 65 | data.str3 = "价格>2" 66 | data.str4 = "价格>3" 67 | data.str5 = "价格>4" 68 | data.str6 = "价格>5" 69 | data.str7 = "价格>6" 70 | data.str8 = "价格>7" 71 | currentData.add(data) 72 | } 73 | 74 | 75 | type4Adapter = Type4Adapter(this, R.layout.item_layout_type4, currentData, mHeadHeaderHSView) 76 | mListView.adapter = type4Adapter 77 | // OnClick监听 78 | mListView.onItemClickListener = OnItemClickListener { arg0, arg1, arg2, arg3 -> 79 | Log.i("Type4Activity ListView", "onItemClick Event") 80 | Toast.makeText(this@Type4Activity, "点了第" + arg2 + "个", 81 | Toast.LENGTH_SHORT).show() 82 | } 83 | 84 | 85 | mListView.setOnScrollListener(object : AbsListView.OnScrollListener { 86 | override fun onScroll(view: AbsListView?, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) { 87 | if (firstVisibleItem >= 1) { 88 | mHeadStickyView.visibility = View.VISIBLE 89 | } else { 90 | mHeadStickyView.visibility = View.GONE 91 | } 92 | } 93 | 94 | override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) { 95 | 96 | } 97 | }) 98 | } 99 | 100 | /** 101 | * TODO 划重点:用来将头部和列表上面的触摸事件都分发给头部的滑动控件 102 | */ 103 | internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener { 104 | 105 | override fun onTouch(arg0: View, arg1: MotionEvent): Boolean { 106 | // 当在列头 和 listView控件上touch时,将这个touch的事件分发给 ScrollView 107 | mHeadHeaderHSView.onTouchEvent(arg1) 108 | mHeadStickyHSView.onTouchEvent(arg1) 109 | return false 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type4/Type4Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type4 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.util.Log 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.BaseAdapter 11 | import android.widget.HorizontalScrollView 12 | import android.widget.TextView 13 | import com.example.kotlin.scrollingtable.R 14 | import com.example.kotlin.scrollingtable.type4.model.ProductData 15 | import com.example.kotlin.scrollingtable.type4.view.SyncHScrollView 16 | 17 | /** 18 | * 列表数据适配器 19 | * Created by xiaoyulaoshi on 2018/1/31. 20 | */ 21 | class Type4Adapter(context: Context, 22 | /** 23 | * layout ID 24 | */ 25 | private val id_row_layout: Int, 26 | /** 27 | * List中的数据 28 | */ 29 | private val currentData: MutableList, 30 | /** 31 | * ListView头部中的横向滑动视图 32 | */ 33 | private val headScrollView: SyncHScrollView) : BaseAdapter() { 34 | private val mInflater: LayoutInflater 35 | 36 | 37 | init { 38 | Log.v(TAG + ".Type4Adapter", " 初始化") 39 | this.mInflater = LayoutInflater.from(context) 40 | 41 | } 42 | 43 | override fun getCount(): Int { 44 | return this.currentData.size 45 | } 46 | 47 | override fun getItem(position: Int): Any? { 48 | return null 49 | } 50 | 51 | override fun getItemId(position: Int): Long { 52 | return 0 53 | } 54 | 55 | /** 56 | * 向List中添加数据 57 | * 58 | * @param items 59 | */ 60 | fun addItem(items: List) { 61 | for (item in items) { 62 | currentData.add(item) 63 | } 64 | } 65 | 66 | /** 67 | * 清空当List中的数据 68 | */ 69 | fun cleanAll() { 70 | this.currentData.clear() 71 | } 72 | 73 | @SuppressLint("SetTextI18n") 74 | override fun getView(position: Int, convertView: View?, parentView: ViewGroup): View { 75 | var convertView = convertView 76 | var holder: ViewHolder? = null 77 | if (convertView == null) { 78 | convertView = mInflater.inflate(id_row_layout, null) 79 | holder = ViewHolder() 80 | 81 | //获取当前条目中的右侧滑动控件 82 | val scrollView1 = convertView!!.findViewById(R.id.horizontalScrollView1) 83 | 84 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 85 | //将当前条目的右侧滑动控件添加到头部滑动控件的滑动观察者集合中 86 | headScrollView.AddOnScrollChangedListener(OnScrollChangedListenerImp(scrollView1)) 87 | 88 | //进行holder的初始化操作 89 | holder.scrollView = scrollView1 90 | holder.txt1 = convertView.findViewById(R.id.textView1) 91 | holder.txt2 = convertView.findViewById(R.id.textView2) 92 | holder.txt3 = convertView.findViewById(R.id.textView3) 93 | holder.txt4 = convertView.findViewById(R.id.textView4) 94 | holder.txt5 = convertView.findViewById(R.id.textView5) 95 | holder.txt6 = convertView.findViewById(R.id.textView6) 96 | holder.txt7 = convertView.findViewById(R.id.textView7) 97 | 98 | convertView.tag = holder 99 | } else { 100 | holder = convertView.tag as ViewHolder 101 | } 102 | holder.txt1!!.text = currentData[position].str1 103 | holder.txt2!!.text = currentData[position].str2!! 104 | holder.txt3!!.text = currentData[position].str3!! 105 | holder.txt4!!.text = currentData[position].str4!! 106 | holder.txt5!!.text = currentData[position].str5!! 107 | holder.txt6!!.text = currentData[position].str6!! 108 | holder.txt7!!.text = currentData[position].str7!! 109 | return convertView 110 | } 111 | 112 | open class OnScrollChangedListenerImp(var mScrollViewArg: SyncHScrollView) : 113 | SyncHScrollView.OnScrollChangedListener { 114 | 115 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 116 | mScrollViewArg.smoothScrollTo(l, t) 117 | } 118 | } 119 | 120 | internal inner class ViewHolder { 121 | var txt1: TextView? = null 122 | var txt2: TextView? = null 123 | var txt3: TextView? = null 124 | var txt4: TextView? = null 125 | var txt5: TextView? = null 126 | var txt6: TextView? = null 127 | var txt7: TextView? = null 128 | var scrollView: HorizontalScrollView? = null 129 | } 130 | 131 | companion object { 132 | private val TAG = Type4Adapter::class.java.name 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type4/model/ProductData.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type4.model 2 | 3 | /** 4 | * Created by xiaoyulaoshi on 2018/1/31. 5 | */ 6 | class ProductData { 7 | //商品类型,-1-分组视图,0-普通的商品,1-特价商品 8 | var typeItem: Int = 0 9 | var str1: String? = null 10 | var str2: String? = null 11 | var str3: String? = null 12 | var str4: String? = null 13 | var str5: String? = null 14 | var str6: String? = null 15 | var str7: String? = null 16 | var str8: String? = null 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type4/view/InterceptScrollContainer.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type4.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import android.widget.LinearLayout 8 | 9 | 10 | /** 11 | * 拦截触摸事件的容器 12 | * Created by xiaoyulaoshi on 2018/1/31. 13 | */ 14 | 15 | class InterceptScrollContainer : LinearLayout { 16 | 17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 18 | 19 | constructor(context: Context) : super(context) {} 20 | 21 | /** 22 | * 拦截TouchEvent 23 | * @see android.view.ViewGroup.onInterceptTouchEvent(android.view.MotionEvent) 24 | */ 25 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 26 | Log.i(TAG, "onInterceptTouchEvent" + ev) 27 | return true 28 | } 29 | 30 | companion object { 31 | private val TAG = InterceptScrollContainer::class.java.name 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type4/view/SyncHScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type4.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.HorizontalScrollView 6 | import java.util.* 7 | 8 | /** 9 | * Created by xiaoyulaoshi on 2018/1/31. 10 | * 11 | * 12 | * 自定义的 滚动控件 13 | * 重载了 [SyncHScrollView.onScrollChanged](滚动条变化),监听每次的变化通知给观察(此变化的)观察者 14 | * 可使用 [SyncHScrollView.AddOnScrollChangedListener] 来订阅本控件的 滚动条变化 15 | */ 16 | class SyncHScrollView : HorizontalScrollView { 17 | internal var mScrollViewObserver: ScrollViewObserver? = ScrollViewObserver() 18 | 19 | constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {} 20 | 21 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 22 | 23 | constructor(context: Context) : super(context) {} 24 | 25 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 26 | /* 27 | * 当滚动条移动后,引发 滚动事件。通知给观察者,观察者会传达给其他的条目中的滚动视图。 28 | */ 29 | if (mScrollViewObserver != null) { 30 | mScrollViewObserver!!.NotifyOnScrollChanged(l, t, oldl, oldt) 31 | } 32 | super.onScrollChanged(l, t, oldl, oldt) 33 | } 34 | 35 | /* 36 | * 订阅 本控件 的 滚动条变化事件 37 | * */ 38 | fun AddOnScrollChangedListener(listener: OnScrollChangedListener) { 39 | mScrollViewObserver!!.AddOnScrollChangedListener(listener) 40 | } 41 | 42 | /* 43 | * 取消 订阅 本控件 的 滚动条变化事件 44 | * */ 45 | fun RemoveOnScrollChangedListener(listener: OnScrollChangedListener) { 46 | mScrollViewObserver!!.RemoveOnScrollChangedListener(listener) 47 | } 48 | 49 | /* 50 | * 当发生了滚动事件时 51 | */ 52 | interface OnScrollChangedListener { 53 | fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) 54 | } 55 | 56 | /** 57 | * 观察者 58 | */ 59 | class ScrollViewObserver { 60 | internal var mList: MutableList? = null 61 | 62 | init { 63 | mList = ArrayList() 64 | } 65 | 66 | fun AddOnScrollChangedListener(listener: OnScrollChangedListener) { 67 | mList!!.add(listener) 68 | } 69 | 70 | fun RemoveOnScrollChangedListener( 71 | listener: OnScrollChangedListener) { 72 | mList!!.remove(listener) 73 | } 74 | 75 | fun NotifyOnScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 76 | if (mList == null || mList!!.size == 0) { 77 | return 78 | } 79 | for (i in mList!!.indices) { 80 | if (mList!![i] != null) { 81 | mList!![i].onScrollChanged(l, t, oldl, oldt) 82 | } 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type5/RvType5Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type5 2 | 3 | import android.view.MotionEvent 4 | import android.view.View 5 | import android.widget.LinearLayout 6 | import android.widget.TextView 7 | import com.chad.library.adapter.base.BaseQuickAdapter 8 | import com.chad.library.adapter.base.BaseViewHolder 9 | import com.example.kotlin.scrollingtable.R 10 | import com.example.kotlin.scrollingtable.type2.model.Type2Model 11 | import com.example.kotlin.scrollingtable.type5.view.SyncHScrollView 12 | 13 | /** 14 | * 每一行的股票信息,第一条是股票名称,之后的是价格信息 15 | * Created by kotlin on 18-1-29. 16 | */ 17 | class RvType5Adapter( 18 | //列表头部需要进行横向滑动关联 19 | var mHeadSyncRecyclerView: SyncHScrollView 20 | ) : BaseQuickAdapter(R.layout.item_layout_type5) { 21 | private var TAG = RvType5Adapter::class.java.name 22 | 23 | 24 | override fun convert(helper: BaseViewHolder, item: Type2Model) { 25 | //当前的条目位置信息 26 | val productPosition = helper.adapterPosition 27 | 28 | helper.setText(R.id.tv_product_name, item.productName) 29 | val ll_item = helper.getView(R.id.ll_item) 30 | ll_item.removeAllViews() 31 | 32 | item.mPriceList?.forEach { 33 | val itemView = getItemView(R.layout.item_layout, null) 34 | itemView.findViewById(R.id.tv_data).text = it 35 | ll_item.addView(itemView) 36 | } 37 | 38 | //右侧横向列表 39 | val itemSyncHScrollView = helper.getView(R.id.hsv_list_right) 40 | 41 | //添加滑动观察者 42 | mHeadSyncRecyclerView.AddOnScrollChangedListener(OnScrollChangedListenerImp(itemSyncHScrollView)) 43 | 44 | 45 | //TODO 为了解决因为设置了Item的点击事件与RecycleView触摸事件冲突导致无法滑动,这里需要对item的触摸事件响应分发,但是效果还是不太好,没有使用ListView流畅 46 | helper.itemView.setOnTouchListener(ListViewAndHeadViewTouchListener()) 47 | } 48 | 49 | internal inner class OnScrollChangedListenerImp(var mScrollViewArg: SyncHScrollView) : 50 | SyncHScrollView.OnScrollChangedListener { 51 | 52 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 53 | mScrollViewArg.smoothScrollTo(l, t) 54 | } 55 | } 56 | 57 | 58 | internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener { 59 | 60 | override fun onTouch(arg0: View, event: MotionEvent): Boolean { 61 | // 当在列头 和 listView控件上touch时,将这个touch的事件分发给 ScrollView 62 | val headScrollView = mHeadSyncRecyclerView 63 | headScrollView.onTouchEvent(event) 64 | return false 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type5/Type5Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type5 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.widget.TextView 10 | import com.example.kotlin.scrollingtable.R 11 | import com.example.kotlin.scrollingtable.type2.model.Type2Model 12 | import kotlinx.android.synthetic.main.activity_type5.* 13 | import kotlinx.android.synthetic.main.item_layout_type5.* 14 | 15 | 16 | class Type5Activity : AppCompatActivity() { 17 | private var TAG = Type5Activity::class.java.name 18 | 19 | 20 | private var mProductAdapter: RvType5Adapter? = null 21 | private var mProductDataList: MutableList? = null 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.activity_type5) 26 | 27 | for (indexTitle in 0..5) { 28 | val titleView = LayoutInflater.from(this).inflate(R.layout.item_layout, null) 29 | titleView.findViewById(R.id.tv_data).text = "表头》${indexTitle}" 30 | ll_item.addView(titleView) 31 | } 32 | 33 | layout_head.isFocusable = true 34 | layout_head.isClickable = true 35 | 36 | layout_head.setOnTouchListener(ListViewAndHeadViewTouchListener()) 37 | rv_list_product.setOnTouchListener(ListViewAndHeadViewTouchListener()) 38 | 39 | 40 | mProductAdapter = RvType5Adapter(hsv_list_right) 41 | mProductDataList = mutableListOf() 42 | for (index in 0..40) { 43 | val productModel = Type2Model() 44 | productModel.productName = "股票名称${index}" 45 | val priceList: MutableList = mutableListOf() 46 | 47 | for (indexPrice in 0..5) { 48 | priceList.add("股票${index}价格${indexPrice}") 49 | } 50 | productModel.mPriceList = priceList 51 | 52 | mProductDataList!!.add(productModel) 53 | } 54 | mProductAdapter!!.setNewData(mProductDataList) 55 | rv_list_product.adapter = mProductAdapter 56 | 57 | //TODO 这里如果设置了点击事件会与设置到RecycleView的OnTouchListener冲突 58 | mProductAdapter?.setOnItemClickListener { adapter, view, position -> 59 | Log.d(TAG, "position>>" + position) 60 | } 61 | 62 | } 63 | 64 | internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener { 65 | 66 | override fun onTouch(arg0: View, event: MotionEvent): Boolean { 67 | // 当在列头 和 listView控件上touch时,将这个touch的事件分发给 ScrollView 68 | val headScrollView = hsv_list_right 69 | headScrollView.onTouchEvent(event) 70 | return false 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type5/view/InterceptScrollLinerLayout.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type5.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import android.widget.LinearLayout 8 | 9 | 10 | /** 11 | * 拦截触摸事件的容器 12 | * Created by xiaoyulaoshi on 2018/1/31. 13 | */ 14 | 15 | class InterceptScrollLinerLayout : LinearLayout { 16 | 17 | constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} 18 | 19 | constructor(context: Context) : super(context) {} 20 | 21 | /** 22 | * 拦截TouchEvent 23 | * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) 24 | */ 25 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 26 | Log.i(TAG, "onInterceptTouchEvent" + ev) 27 | return true 28 | } 29 | 30 | companion object { 31 | private val TAG = InterceptScrollLinerLayout::class.java.name 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type5/view/SyncHScrollView.java: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type5.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.widget.HorizontalScrollView; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by xiaoyulaoshi on 2018/1/31. 13 | * 自定义的 滚动控件 14 | * 重载了 {@link SyncHScrollView#onScrollChanged}(滚动条变化),监听每次的变化通知给观察(此变化的)观察者 15 | * 可使用 {@link SyncHScrollView#AddOnScrollChangedListener(SyncHScrollView.OnScrollChangedListener) } 来订阅本控件的 滚动条变化 16 | */ 17 | 18 | public class SyncHScrollView extends HorizontalScrollView { 19 | SyncHScrollView.ScrollViewObserver mScrollViewObserver = new SyncHScrollView.ScrollViewObserver(); 20 | 21 | public SyncHScrollView(Context context, AttributeSet attrs, int defStyle) { 22 | super(context, attrs, defStyle); 23 | } 24 | 25 | public SyncHScrollView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public SyncHScrollView(Context context) { 30 | super(context); 31 | } 32 | 33 | @Override 34 | public boolean onTouchEvent(MotionEvent ev) { 35 | return super.onTouchEvent(ev); 36 | } 37 | 38 | @Override 39 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 40 | /* 41 | * 当滚动条移动后,引发 滚动事件。通知给观察者,观察者会传达给其他的条目中的滚动视图。 42 | */ 43 | if (mScrollViewObserver != null) { 44 | mScrollViewObserver.NotifyOnScrollChanged(l, t, oldl, oldt); 45 | } 46 | super.onScrollChanged(l, t, oldl, oldt); 47 | } 48 | 49 | /* 50 | * 订阅 本控件 的 滚动条变化事件 51 | * */ 52 | public void AddOnScrollChangedListener(SyncHScrollView.OnScrollChangedListener listener) { 53 | mScrollViewObserver.AddOnScrollChangedListener(listener); 54 | } 55 | 56 | /* 57 | * 取消 订阅 本控件 的 滚动条变化事件 58 | * */ 59 | public void RemoveOnScrollChangedListener(SyncHScrollView.OnScrollChangedListener listener) { 60 | mScrollViewObserver.RemoveOnScrollChangedListener(listener); 61 | } 62 | 63 | /* 64 | * 当发生了滚动事件时 65 | */ 66 | public static interface OnScrollChangedListener { 67 | public void onScrollChanged(int l, int t, int oldl, int oldt); 68 | } 69 | 70 | /** 71 | * 观察者 72 | */ 73 | public static class ScrollViewObserver { 74 | List mList; 75 | 76 | public ScrollViewObserver() { 77 | super(); 78 | mList = new ArrayList<>(); 79 | } 80 | 81 | public void AddOnScrollChangedListener(SyncHScrollView.OnScrollChangedListener listener) { 82 | mList.add(listener); 83 | } 84 | 85 | public void RemoveOnScrollChangedListener( 86 | SyncHScrollView.OnScrollChangedListener listener) { 87 | mList.remove(listener); 88 | } 89 | 90 | public void NotifyOnScrollChanged(int l, int t, int oldl, int oldt) { 91 | if (mList == null || mList.size() == 0) { 92 | return; 93 | } 94 | for (int i = 0; i < mList.size(); i++) { 95 | if (mList.get(i) != null) { 96 | mList.get(i).onScrollChanged(l, t, oldl, oldt); 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type5/view/SyncRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type5.view; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | import android.util.Log; 8 | import android.view.MotionEvent; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * 可以实现关联滑动的RecyclerView,实现方式为观察者机制 15 | * Created by xiaoyulaoshi on 2018/1/31. 16 | */ 17 | 18 | public class SyncRecyclerView extends RecyclerView { 19 | 20 | private final String TAG = SyncRecyclerView.class.getName(); 21 | 22 | //观察者 23 | private SyncRecyclerViewObserver mSyncRecyclerViewObserver = new SyncRecyclerViewObserver(); 24 | 25 | 26 | public SyncRecyclerView(Context context) { 27 | super(context); 28 | } 29 | 30 | public SyncRecyclerView(Context context, @Nullable AttributeSet attrs) { 31 | super(context, attrs); 32 | } 33 | 34 | public SyncRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 35 | super(context, attrs, defStyle); 36 | } 37 | 38 | 39 | @Override 40 | public boolean onTouchEvent(MotionEvent e) { 41 | return super.onTouchEvent(e); 42 | } 43 | 44 | @Override 45 | public void onScrolled(int dx, int dy) { 46 | Log.i(TAG, "onScrolled >> dx=" + dx + "\tdy=" + dy); 47 | //将发生的滑动改变通知到观察者中的每一个滑动监听 48 | if (mSyncRecyclerViewObserver != null) { 49 | mSyncRecyclerViewObserver.notifyOnScrollChange(dx, dy); 50 | } 51 | super.onScrolled(dx, dy); 52 | } 53 | 54 | @Override 55 | protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 56 | super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); 57 | } 58 | 59 | /** 60 | * 向观察者中添加 当前控件的滑动事件监听 61 | */ 62 | public void addOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) { 63 | mSyncRecyclerViewObserver.addOnScrollChangeListener(onScrollChangeListener); 64 | } 65 | 66 | 67 | /** 68 | * 移除观察者中 当前控件的滑动事件监听 69 | */ 70 | public void removeOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) { 71 | mSyncRecyclerViewObserver.removeOnScrollChangeListener(onScrollChangeListener); 72 | } 73 | 74 | 75 | /** 76 | * 自定义滑动监听 77 | */ 78 | public interface OnScrollChangeListener { 79 | void onScrollChanged(int dx, int dy); 80 | } 81 | 82 | /** 83 | * 滑动观察者,用来多个列表关联滑动的实现 84 | */ 85 | public static class SyncRecyclerViewObserver { 86 | //创建关联监听的集合,因为每一个列表都会设置监听器 87 | List mList; 88 | 89 | public SyncRecyclerViewObserver() { 90 | super(); 91 | mList = new ArrayList<>(); 92 | } 93 | 94 | /** 95 | * 添加滑动监听器 96 | * 97 | * @param onScrollChangeListener 滑动监听器 98 | */ 99 | public void addOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) { 100 | mList.add(onScrollChangeListener); 101 | } 102 | 103 | /** 104 | * 移除滑动监听器 105 | * 106 | * @param onScrollChangeListener 要删除的滑动监听器 107 | */ 108 | public void removeOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) { 109 | mList.remove(onScrollChangeListener); 110 | } 111 | 112 | /** 113 | * 广播滑动改变,通知到每一个滑动监听器 114 | */ 115 | public void notifyOnScrollChange(int dx, int dy) { 116 | if (mList == null || mList.size() == 0) { 117 | return; 118 | } 119 | 120 | //循环通知每一个滑动监听器 121 | for (int i = 0; i < mList.size(); i++) { 122 | if (mList.get(i) != null) { 123 | mList.get(i).onScrollChanged(dx, dy); 124 | } 125 | } 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type6/Type6Activity.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type6 2 | 3 | 4 | import android.app.Activity 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.View 8 | import android.widget.AbsListView 9 | import android.widget.AdapterView.OnItemClickListener 10 | import android.widget.ListView 11 | import android.widget.Toast 12 | import com.example.kotlin.scrollingtable.R 13 | import com.example.kotlin.scrollingtable.type4.model.ProductData 14 | import com.example.kotlin.scrollingtable.type4.view.SyncHScrollView 15 | import java.util.* 16 | 17 | /** 18 | * 最完美实现,使用 ListView + HorizontalScrollView 实现 19 | * Created by xiaoyulaoshi on 2018/1/31. 20 | */ 21 | class Type6Activity : Activity() { 22 | 23 | //头部吸顶视图 24 | internal lateinit var mHeadStickyView: View 25 | //头部吸顶的右侧滑动视图 26 | internal lateinit var mHeadStickyHSView: SyncHScrollView 27 | //列表视图 28 | internal lateinit var mListView: ListView 29 | //数据适配器 30 | internal lateinit var type4Adapter: Type6Adapter 31 | 32 | public override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(R.layout.activity_type6) 35 | 36 | //头部吸顶视图 37 | mHeadStickyView = findViewById(R.id.head) 38 | mHeadStickyHSView = findViewById(R.id.horizontalScrollView1) 39 | 40 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 41 | 42 | mListView = findViewById(R.id.lv_produce) 43 | 44 | // 创建当前用于显示视图的数据 45 | val currentData = ArrayList() 46 | for (i in 0..49) { 47 | val data = ProductData() 48 | data.str1 = "股票>" + i 49 | data.str2 = "价格>1" 50 | data.str3 = "价格>2" 51 | data.str4 = "价格>3" 52 | data.str5 = "价格>4" 53 | data.str6 = "价格>5" 54 | data.str7 = "价格>6" 55 | data.str8 = "价格>7" 56 | currentData.add(data) 57 | } 58 | 59 | val data = ProductData() 60 | data.typeItem = 1 61 | currentData.add(0, data) 62 | currentData.add(1, data) 63 | 64 | val dataHead = ProductData() 65 | dataHead.typeItem = 2 66 | currentData.add(2, dataHead) 67 | 68 | currentData.add(10, data) 69 | currentData.add(11, data) 70 | 71 | 72 | type4Adapter = Type6Adapter(this, currentData, mListView, mHeadStickyHSView) 73 | mListView.adapter = type4Adapter 74 | // OnClick监听 75 | mListView.onItemClickListener = OnItemClickListener { arg0, arg1, arg2, arg3 -> 76 | Log.i("Type4Activity ListView", "onItemClick Event") 77 | Toast.makeText(this@Type6Activity, "点了第" + arg2 + "个", 78 | Toast.LENGTH_SHORT).show() 79 | } 80 | 81 | 82 | mListView.setOnScrollListener(object : AbsListView.OnScrollListener { 83 | override fun onScroll(view: AbsListView?, firstVisibleItem: Int, visibleItemCount: Int, totalItemCount: Int) { 84 | if (currentData[firstVisibleItem].typeItem == 0) { 85 | mHeadStickyView.visibility = View.VISIBLE 86 | } else { 87 | mHeadStickyView.visibility = View.GONE 88 | } 89 | } 90 | 91 | override fun onScrollStateChanged(view: AbsListView?, scrollState: Int) { 92 | 93 | } 94 | }) 95 | } 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/kotlin/scrollingtable/type6/Type6Adapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.kotlin.scrollingtable.type6 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.content.Context 6 | import android.view.LayoutInflater 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.* 11 | import com.example.kotlin.scrollingtable.R 12 | import com.example.kotlin.scrollingtable.type4.model.ProductData 13 | import com.example.kotlin.scrollingtable.type4.view.SyncHScrollView 14 | 15 | /** 16 | * 列表数据适配器 17 | * Created by xiaoyulaoshi on 2018/1/31. 18 | */ 19 | class Type6Adapter(context: Context, 20 | /** 21 | * List中的数据 22 | */ 23 | private val currentData: MutableList, 24 | mListView: ListView, 25 | private var mHeadStickyHSView: SyncHScrollView?) : BaseAdapter() { 26 | private val mInflater: LayoutInflater = LayoutInflater.from(context) 27 | 28 | //列表中的分组头部右侧的滑动视图 29 | private var mHeadGroupScrollView: SyncHScrollView? = null 30 | 31 | init { 32 | mListView.setOnTouchListener(ListViewAndHeadViewTouchListener()) 33 | mHeadStickyHSView?.setOnTouchListener(ListViewAndHeadViewTouchListener()) 34 | } 35 | 36 | override fun getCount(): Int { 37 | return this.currentData.size 38 | } 39 | 40 | override fun getItem(position: Int): Any? { 41 | return currentData[position] 42 | } 43 | 44 | override fun getItemId(position: Int): Long { 45 | return 0 46 | } 47 | 48 | // 每个convert view都会调用此方法,获得当前所需要的view样式 49 | override fun getItemViewType(position: Int): Int { 50 | return currentData[position].typeItem 51 | } 52 | 53 | override fun getViewTypeCount(): Int { 54 | return 3 55 | } 56 | 57 | 58 | @SuppressLint("SetTextI18n") 59 | override fun getView(position: Int, convertView: View?, parentView: ViewGroup): View? { 60 | var convertView = convertView 61 | var holder1: ViewHolder1? = null 62 | var holder2: ViewHolder2? = null 63 | var holder3: ViewHolder3? = null 64 | 65 | //获取当前的条目数据类型 66 | val typeItem = getItemViewType(position) 67 | 68 | if (convertView == null) { 69 | when (typeItem) { 70 | 0 -> { 71 | convertView = mInflater.inflate(R.layout.item_layout_type4, null) 72 | holder1 = ViewHolder1() 73 | 74 | //获取当前条目中的右侧滑动控件 75 | val scrollView1 = convertView!!.findViewById(R.id.horizontalScrollView1) 76 | //TODO 划重点:这里需要从传入的列表头拿到里面的右侧滑动控件 77 | //将当前条目的右侧滑动控件添加到头部滑动控件的滑动观察者集合中 78 | mHeadGroupScrollView?.AddOnScrollChangedListener(OnScrollChangedListenerImp(scrollView1)) 79 | mHeadStickyHSView?.AddOnScrollChangedListener(OnScrollChangedListenerImp(scrollView1)) 80 | 81 | //进行holder的初始化操作 82 | holder1.scrollView = scrollView1 83 | holder1.txt1 = convertView.findViewById(R.id.textView1) 84 | holder1.txt2 = convertView.findViewById(R.id.textView2) 85 | holder1.txt3 = convertView.findViewById(R.id.textView3) 86 | holder1.txt4 = convertView.findViewById(R.id.textView4) 87 | holder1.txt5 = convertView.findViewById(R.id.textView5) 88 | holder1.txt6 = convertView.findViewById(R.id.textView6) 89 | holder1.txt7 = convertView.findViewById(R.id.textView7) 90 | 91 | convertView.tag = holder1 92 | } 93 | 94 | 1 -> { 95 | convertView = mInflater.inflate(R.layout.item_layout_type6, null) 96 | holder2 = ViewHolder2() 97 | 98 | convertView.tag = holder2 99 | } 100 | 101 | 2 -> { 102 | convertView = mInflater.inflate(R.layout.item_layout_type6_group, null) 103 | holder3 = ViewHolder3() 104 | 105 | mHeadGroupScrollView = convertView.findViewById(R.id.horizontalScrollView1) 106 | holder3.scrollView = convertView.findViewById(R.id.horizontalScrollView1) 107 | convertView.tag = holder3 108 | } 109 | } 110 | 111 | } else { 112 | when (typeItem) { 113 | 0 -> { 114 | holder1 = convertView.tag as ViewHolder1 115 | } 116 | 1 -> { 117 | holder2 = convertView.tag as ViewHolder2 118 | } 119 | 2 -> { 120 | holder3 = convertView.tag as ViewHolder3 121 | } 122 | } 123 | } 124 | when (typeItem) { 125 | 0 -> { 126 | holder1?.txt1!!.text = currentData[position].str1 127 | holder1.txt2!!.text = currentData[position].str2!! 128 | holder1.txt3!!.text = currentData[position].str3!! 129 | holder1.txt4!!.text = currentData[position].str4!! 130 | holder1.txt5!!.text = currentData[position].str5!! 131 | holder1.txt6!!.text = currentData[position].str6!! 132 | holder1.txt7!!.text = currentData[position].str7!! 133 | } 134 | } 135 | return convertView 136 | } 137 | 138 | open class OnScrollChangedListenerImp(var mScrollViewArg: SyncHScrollView) : 139 | SyncHScrollView.OnScrollChangedListener { 140 | 141 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 142 | mScrollViewArg.smoothScrollTo(l, t) 143 | } 144 | } 145 | 146 | /** 147 | * TODO 划重点:用来将头部和列表上面的触摸事件都分发给头部的滑动控件 148 | */ 149 | internal inner class ListViewAndHeadViewTouchListener : View.OnTouchListener { 150 | 151 | override fun onTouch(arg0: View, arg1: MotionEvent): Boolean { 152 | // 当在列头 和 listView控件上touch时,将这个touch的事件分发给 ScrollView 153 | mHeadGroupScrollView?.onTouchEvent(arg1) 154 | mHeadStickyHSView?.onTouchEvent(arg1) 155 | return false 156 | } 157 | } 158 | 159 | internal inner class ViewHolder1 { 160 | var txt1: TextView? = null 161 | var txt2: TextView? = null 162 | var txt3: TextView? = null 163 | var txt4: TextView? = null 164 | var txt5: TextView? = null 165 | var txt6: TextView? = null 166 | var txt7: TextView? = null 167 | var scrollView: HorizontalScrollView? = null 168 | } 169 | 170 | internal inner class ViewHolder2 { 171 | var txt1: TextView? = null 172 | var iv_icon: ImageView? = null 173 | } 174 | 175 | internal inner class ViewHolder3 { 176 | //右侧的滑动控件 177 | var scrollView: SyncHScrollView? = null 178 | 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /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/selector_bg_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_bg_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |