├── .gitattributes ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zyyoona7 │ │ └── customviewsets │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zyyoona7 │ │ │ └── customviewsets │ │ │ ├── BaseActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── basic_operation │ │ │ ├── BasicOperationActivity.java │ │ │ ├── CustomBasicOpView.java │ │ │ └── CustomCoordinateSystem.java │ │ │ ├── cover_flow │ │ │ ├── Collector.java │ │ │ ├── CoverFlowActivity.java │ │ │ ├── HorizontalCarouselLayout.java │ │ │ ├── HorizontalCarouselStyle.java │ │ │ └── ScrollerLayout.java │ │ │ ├── custom_layout_manager │ │ │ ├── CardActivity.java │ │ │ ├── CardItemView.java │ │ │ ├── CardLayoutManager.java │ │ │ └── Pool.java │ │ │ ├── heart │ │ │ ├── Heart.java │ │ │ ├── HeartActivity.java │ │ │ └── HeartView.java │ │ │ ├── pagination_rv │ │ │ ├── CustomAdapter.java │ │ │ ├── CustomLayoutManager.java │ │ │ ├── LayoutManagerActivity.java │ │ │ ├── PaginationSnapHelper.java │ │ │ └── SnappyRecyclerView.java │ │ │ ├── utils │ │ │ └── DensityUtils.java │ │ │ └── zoom_hover │ │ │ ├── OnItemSelectedListener.java │ │ │ ├── OnZoomAnimatorListener.java │ │ │ ├── TestZoomHoverAdapter.java │ │ │ ├── ZoomHoverActivity.java │ │ │ ├── ZoomHoverAdapter.java │ │ │ ├── ZoomHoverGridView.java │ │ │ ├── ZoomHoverItem.java │ │ │ ├── ZoomHoverSpan.java │ │ │ └── ZoomHoverView.java │ └── res │ │ ├── drawable-xxhdpi │ │ ├── heart_default.png │ │ ├── ss_heart1.png │ │ ├── ss_heart2.png │ │ ├── ss_heart3.png │ │ ├── ss_heart4.png │ │ └── ss_heart5.png │ │ ├── drawable │ │ └── shadow_bg.xml │ │ ├── layout │ │ ├── activity_basic_operation.xml │ │ ├── activity_card.xml │ │ ├── activity_cover_flow.xml │ │ ├── activity_heart.xml │ │ ├── activity_layout_manager.xml │ │ ├── activity_main.xml │ │ ├── activity_zoom_hover.xml │ │ ├── item.xml │ │ ├── item_overlay_card.xml │ │ └── item_pagination.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── yoona.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zyyoona7 │ └── customviewsets │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── basic_operation.gif ├── cardLayoutManager.gif ├── heart_view.gif ├── paginationRv.gif └── zoomhover.gif └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.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 {yyyy} {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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 自定义View集合 2 | 3 | ![Basic_Opreation](https://github.com/zyyoona7/CustomViewSets/blob/master/images/basic_operation.gif) 4 | 5 | ### ZoomHoverView 6 | 7 | **click to zoom in and float view 点击放大悬浮的自定义View** 8 | 9 | ![ZoomHoverView](https://github.com/zyyoona7/CustomViewSets/blob/master/images/zoomhover.gif) 10 | 11 | ### Usage 12 | 13 | ### ZoomHoverView 14 | **layout:** 15 | ```xml 16 | 27 | 28 | 29 | ``` 30 | **java** 31 | ```java 32 | mAdapter = new TestZoomHoverAdapter(mList); 33 | final SimpleArrayMap map = new SimpleArrayMap<>(); 34 | map.put(0, 2); 35 | mZoomHoverView.setSpan(map); 36 | mZoomHoverView.setAdapter(mAdapter); 37 | //设置动画监听 38 | mZoomHoverView.setOnZoomAnimatorListener(new OnZoomAnimatorListener() { 39 | @Override 40 | public void onZoomInStart(View view) { 41 | //放大动画开始 42 | view.setBackground(getResources().getDrawable(android.R.drawable.dialog_holo_light_frame)); 43 | } 44 | 45 | @Override 46 | public void onZoomInEnd(View view) { 47 | 48 | } 49 | 50 | @Override 51 | public void onZoomOutStart(View view) { 52 | //缩小动画开始 53 | } 54 | 55 | @Override 56 | public void onZoomOutEnd(View view) { 57 | view.setBackgroundColor(getResources().getColor(R.color.colorAccent)); 58 | } 59 | }); 60 | 61 | mZoomHoverView.setOnItemSelectedListener(new OnItemSelectedListener() { 62 | @Override 63 | public void onItemSelected(View view, int position) { 64 | Toast.makeText(ZoomHoverActivity.this,"selected position="+position,Toast.LENGTH_SHORT).show(); 65 | } 66 | }); 67 | 68 | //设置放大动画插值器 69 | mZoomHoverView.setZoomInInterpolator(interpolator); 70 | //设置缩小动画插值器 71 | mZoomHoverView.setZoomOutInterpolator(interpolator); 72 | //同时设置两个动画的插值器 73 | mZoomHoverView.setZoomInterpolator(interpolator); 74 | //设置选中的item 75 | mZoomHoverView.setSelectedItem(position); 76 | ``` 77 | ### ZoomHoverGridView 78 | **layout** 79 | ```xml 80 | 93 | 94 | 95 | ``` 96 | **java** 97 | ```java 98 | mZoomHoverGridView.addSpanItem(0, 1, 2); 99 | mZoomHoverGridView.addSpanItem(3, 2, 2); 100 | mZoomHoverGridView.addSpanItem(4, 2, 1); 101 | mZoomHoverGridView.addSpanItem(5, 1, 3); 102 | 103 | mZoomHoverGridView.setAdapter(mAdapter); 104 | ``` 105 | **自定义属性** 106 | ```xml 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | ``` 126 | ### Changed 127 | - 新增**ZoomHoverGridView**继承自GridLayout,可以实现拉伸行和列 128 | - 修改添加拉伸的方法,放进了view中(原来在adapter中) 129 | 130 | ### Thanks 131 | [GridBuilder](https://github.com/Eason90/GridBuilder) 132 | 133 | ### CardLayoutManager 134 | 135 | **代码完全从[CardLayoutManager](https://github.com/qibin0506/CardLayoutManager)复制,谢谢作者** 136 | 137 | ![CardLayoutManager](https://github.com/zyyoona7/CustomViewSets/blob/master/images/cardLayoutManager.gif) 138 | 139 | ### Horizontal Pagination RecyclerView 140 | 141 | ![PaginationRv](https://github.com/zyyoona7/CustomViewSets/blob/master/images/paginationRv.gif) 142 | 143 | ### HeartView 144 | ![](https://github.com/zyyoona7/CustomViewSets/blob/master/images/heart_view.gif) 145 | 146 | ### License 147 | ``` 148 | Copyright 2017 zyyoona7 149 | 150 | Licensed under the Apache License, Version 2.0 (the "License"); 151 | you may not use this file except in compliance with the License. 152 | You may obtain a copy of the License at 153 | 154 | http://www.apache.org/licenses/LICENSE-2.0 155 | 156 | Unless required by applicable law or agreed to in writing, software 157 | distributed under the License is distributed on an "AS IS" BASIS, 158 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 159 | See the License for the specific language governing permissions and 160 | limitations under the License. 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'android-apt' 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion '25.0.0' 6 | defaultConfig { 7 | applicationId "com.zyyoona7.customviewsets" 8 | minSdkVersion 19 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | renderscriptTargetApi 20 14 | renderscriptSupportModeEnabled true 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 28 | exclude group: 'com.android.support', module: 'support-annotations' 29 | }) 30 | compile 'com.android.support:design:25.2.0' 31 | compile 'com.android.support:design:25.2.0' 32 | compile 'com.android.support:appcompat-v7:25.2.0' 33 | compile 'com.jakewharton:butterknife:8.2.1' 34 | compile 'de.greenrobot:androidsvg:1.2.2-beta-1-tweaked-2' 35 | compile 'com.nineoldandroids:library:2.4.0' 36 | compile 'com.android.support:cardview-v7:25.2.0' 37 | compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.1.3' 38 | compile 'com.daasuu:BubbleLayout:1.1.1' 39 | compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha3' 40 | testCompile 'junit:junit:4.12' 41 | apt 'com.jakewharton:butterknife-compiler:8.2.1' 42 | } 43 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidIDE\android-sdk-windows/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zyyoona7/customviewsets/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.zyyoona7.customviewsets", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | import butterknife.ButterKnife; 9 | import butterknife.Unbinder; 10 | 11 | /** 12 | * Created by zyyoona7 on 2016/8/9. 13 | */ 14 | 15 | public abstract class BaseActivity extends AppCompatActivity { 16 | 17 | private Unbinder mUnbinder; 18 | 19 | @Override 20 | protected void onCreate(@Nullable Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | if (getContentViewID() != 0) { 23 | setContentView(getContentViewID()); 24 | } 25 | mUnbinder= ButterKnife.bind(this); 26 | } 27 | 28 | @Override 29 | protected void onDestroy() { 30 | super.onDestroy(); 31 | if(mUnbinder!=null){ 32 | mUnbinder.unbind(); 33 | } 34 | } 35 | 36 | protected void goTo(Class clazz){ 37 | startActivity(new Intent(this,clazz)); 38 | } 39 | 40 | protected abstract int getContentViewID(); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Button; 5 | 6 | import com.zyyoona7.customviewsets.basic_operation.BasicOperationActivity; 7 | import com.zyyoona7.customviewsets.custom_layout_manager.CardActivity; 8 | import com.zyyoona7.customviewsets.heart.HeartActivity; 9 | import com.zyyoona7.customviewsets.pagination_rv.LayoutManagerActivity; 10 | import com.zyyoona7.customviewsets.zoom_hover.ZoomHoverActivity; 11 | 12 | import butterknife.BindView; 13 | import butterknife.OnClick; 14 | 15 | public class MainActivity extends BaseActivity { 16 | @BindView(R.id.btn_basic_operation) 17 | Button mBtnBasicOp; 18 | 19 | @BindView(R.id.btn_overlay_card) 20 | Button mBtnOverlayCard; 21 | 22 | @BindView(R.id.btn_card_layout_manager) 23 | Button mBtnCardLayoutManager; 24 | @BindView(R.id.btn_paging) 25 | Button mBtnPaging; 26 | @BindView(R.id.btn_heart) 27 | Button mBtnHeart; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | } 33 | 34 | @Override 35 | protected int getContentViewID() { 36 | return R.layout.activity_main; 37 | } 38 | 39 | @OnClick(R.id.btn_basic_operation) 40 | void basicOpClick() { 41 | goTo(BasicOperationActivity.class); 42 | } 43 | 44 | @OnClick(R.id.btn_overlay_card) 45 | void overlayCardClick() { 46 | goTo(ZoomHoverActivity.class); 47 | } 48 | 49 | @OnClick(R.id.btn_card_layout_manager) 50 | void cardLayoutManagerBtnClick() { 51 | goTo(CardActivity.class); 52 | } 53 | 54 | @OnClick(R.id.btn_paging) 55 | void pagingBtnClick() { 56 | goTo(LayoutManagerActivity.class); 57 | } 58 | 59 | @OnClick(R.id.btn_heart) 60 | void heartBtnClick() { 61 | goTo(HeartActivity.class); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/basic_operation/BasicOperationActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.basic_operation; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.zyyoona7.customviewsets.R; 7 | 8 | public class BasicOperationActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_basic_operation); 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/basic_operation/CustomBasicOpView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.basic_operation; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Point; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | import com.zyyoona7.customviewsets.utils.DensityUtils; 14 | 15 | /** 16 | * Created by zyyoona7 on 2016/8/10. 17 | *

18 | * view基本操作图形集合 19 | */ 20 | 21 | public class CustomBasicOpView extends View { 22 | 23 | private static final int PAINT_COLOR = 0xff0000ff; 24 | private static final int TEXT_SIZE = 14; 25 | private static final int STOKE_WIDTH = 5; 26 | //长方形的宽高 27 | private int rectWidth = 0; 28 | private int rectHeight = 0; 29 | //间距 30 | private int margin = 0; 31 | 32 | //同坐标系的padding 33 | private int padding = 0; 34 | 35 | //画笔 36 | private Paint mPaint; 37 | 38 | //宽高 39 | private int mWidth = 0; 40 | private int mHeight = 0; 41 | /** 42 | * 利用这三个点来划分画图的范围(防止和坐标轴的图象重叠) 43 | */ 44 | //起始xy点 45 | private Point startXYPoint = null; 46 | //结束的x轴点 47 | private Point endXPoint = null; 48 | //结束的y轴点 49 | private Point endYPoint = null; 50 | 51 | private int mTextHeight = 0; 52 | 53 | public CustomBasicOpView(Context context) { 54 | super(context); 55 | initPaint(); 56 | } 57 | 58 | public CustomBasicOpView(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | initPaint(); 61 | mTextHeight = DensityUtils.sp2px(context, TEXT_SIZE); 62 | rectWidth = DensityUtils.dip2px(context, 150); 63 | rectHeight = DensityUtils.dip2px(context, 75); 64 | margin = DensityUtils.dip2px(context, 8); 65 | padding = DensityUtils.dip2px(context, 5); 66 | } 67 | 68 | private void initPaint() { 69 | mPaint = new Paint(); 70 | mPaint.setAntiAlias(true); 71 | mPaint.setStyle(Paint.Style.STROKE); 72 | mPaint.setStrokeWidth(STOKE_WIDTH); 73 | mPaint.setStrokeCap(Paint.Cap.ROUND); 74 | mPaint.setColor(PAINT_COLOR); 75 | 76 | } 77 | 78 | /** 79 | * 改变成画字体 80 | */ 81 | private void changeToTextPaint() { 82 | mPaint.setStyle(Paint.Style.FILL); 83 | mPaint.setTextSize(mTextHeight); 84 | mPaint.setColor(Color.BLACK); 85 | } 86 | 87 | private void changeStyleFill() { 88 | mPaint.setStyle(Paint.Style.FILL); 89 | } 90 | 91 | private void changeStyleStroke() { 92 | mPaint.setStyle(Paint.Style.STROKE); 93 | } 94 | 95 | private void changeColorRed() { 96 | mPaint.setColor(Color.RED); 97 | } 98 | 99 | private void changeColorBlue() { 100 | mPaint.setColor(Color.BLUE); 101 | } 102 | 103 | private void changeColorGray() { 104 | mPaint.setColor(Color.GRAY); 105 | } 106 | 107 | @Override 108 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 109 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 110 | } 111 | 112 | @Override 113 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 114 | super.onSizeChanged(w, h, oldw, oldh); 115 | mWidth = w; 116 | mHeight = h; 117 | startXYPoint = new Point(mWidth/2-rectWidth/2, 3 * margin + padding + DensityUtils.sp2px(getContext(), 16)); 118 | endXPoint = new Point(mWidth - (3 * margin + padding), 3 * margin + padding + DensityUtils.sp2px(getContext(), 16)); 119 | endYPoint = new Point(3 * margin + padding, mHeight - (3 * margin + padding + DensityUtils.sp2px(getContext(), 16))); 120 | } 121 | 122 | @Override 123 | protected void onDraw(Canvas canvas) { 124 | //画点 125 | canvas.save(); 126 | changeColorBlue(); 127 | canvas.drawPoint(startXYPoint.x + margin, startXYPoint.y + margin, mPaint); 128 | canvas.drawPoint(startXYPoint.x + 3 * margin, startXYPoint.y + margin, mPaint); 129 | canvas.drawPoint(startXYPoint.x + 5 * margin, startXYPoint.y + margin, mPaint); 130 | canvas.restore(); 131 | 132 | //画线 133 | canvas.save(); 134 | canvas.drawLine(startXYPoint.x, startXYPoint.y + 3 * margin, startXYPoint.x + rectWidth, startXYPoint.y + 3 * margin, mPaint); 135 | canvas.drawLine(startXYPoint.x, startXYPoint.y + 4 * margin, startXYPoint.x + 2 * rectWidth / 3, startXYPoint.y + 4 * margin, mPaint); 136 | canvas.drawLine(startXYPoint.x, startXYPoint.y + 5 * margin, startXYPoint.x + rectWidth / 2, startXYPoint.y + 5 * margin, mPaint); 137 | canvas.restore(); 138 | 139 | //画长方形 140 | canvas.save(); 141 | //空心长方形 142 | Rect rect = new Rect(startXYPoint.x, startXYPoint.y + 6 * margin, startXYPoint.x + rectWidth + 10, startXYPoint.y + rectHeight + 6 * margin); 143 | Rect rect1 = new Rect(startXYPoint.x, startXYPoint.y + rectHeight + 7 * margin, startXYPoint.x + rectWidth + 10, startXYPoint.y + 2 * rectHeight + 7 * margin); 144 | mPaint.setStyle(Paint.Style.STROKE); 145 | canvas.drawRect(rect, mPaint); 146 | changeToTextPaint(); 147 | String strHollowRect = "我是空心长方形,Paint的style为STROKE"; 148 | int hollowRectTextWidth = (int) mPaint.measureText(strHollowRect); 149 | //画文字 150 | canvas.drawText(strHollowRect, (rectWidth - hollowRectTextWidth) / 2 + startXYPoint.x, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 6 * margin + mTextHeight , mPaint); 151 | //实心长方形 152 | changeColorBlue(); 153 | canvas.drawRect(rect1, mPaint); 154 | changeToTextPaint(); 155 | String strRect = "我是实心长方形,Paint的style为FILL"; 156 | int rectWidth1 = (int) mPaint.measureText(strRect); 157 | canvas.drawText(strRect, (rectWidth - rectWidth1) / 2 + startXYPoint.x + 10, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 7 * margin + rectHeight + mTextHeight , mPaint); 158 | canvas.restore(); 159 | 160 | //画正方形 161 | changeColorBlue(); 162 | Rect rect2 = new Rect(startXYPoint.x+rectHeight/2, startXYPoint.y + 8 * margin + 2 * rectHeight, startXYPoint.x + rectHeight+rectHeight/2, startXYPoint.y + 8 * margin + 3 * rectHeight); 163 | canvas.drawRect(rect2, mPaint); 164 | changeToTextPaint(); 165 | String strSquare = "我是实心正方形"; 166 | int squareWidth = (int) mPaint.measureText(strSquare); 167 | canvas.drawText(strSquare, (rectHeight - squareWidth) / 2 + startXYPoint.x+rectHeight/2, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 8 * margin + 2 * rectHeight + mTextHeight , mPaint); 168 | 169 | //画椭圆 170 | RectF rect3 = new RectF(startXYPoint.x, startXYPoint.y + 9 * margin + 3 * rectHeight, startXYPoint.x + rectWidth, startXYPoint.y + 9 * margin + 4 * rectHeight); 171 | changeColorGray(); 172 | canvas.drawRect(rect3, mPaint); 173 | changeColorBlue(); 174 | canvas.drawOval(rect3, mPaint); 175 | changeToTextPaint(); 176 | String strOval = "我是椭圆,根据我的外围长方形画出"; 177 | int ovalWidth = (int) mPaint.measureText(strOval); 178 | canvas.drawText(strOval, (rectWidth - ovalWidth) / 2 + startXYPoint.x, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 9 * margin + 3 * rectHeight + mTextHeight , mPaint); 179 | 180 | //画圆 181 | Rect rect4 = new Rect(startXYPoint.x+rectHeight/2, startXYPoint.y + 10 * margin + 4 * rectHeight, startXYPoint.x+rectHeight/2 + rectHeight, startXYPoint.y + 10 * margin + 5 * rectHeight); 182 | changeColorGray(); 183 | canvas.drawRect(rect4, mPaint); 184 | changeColorBlue(); 185 | canvas.drawCircle(startXYPoint.x+rectHeight/2 + rectHeight / 2, startXYPoint.y + 10 * margin + 4 * rectHeight + rectHeight / 2, rectHeight / 2, mPaint); 186 | changeToTextPaint(); 187 | String strCircle = "我是圆"; 188 | int circleWidth = (int) mPaint.measureText(strCircle); 189 | canvas.drawText(strCircle, (rectHeight - circleWidth) / 2 + startXYPoint.x+rectHeight/2, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 10 * margin + 4 * rectHeight + mTextHeight , mPaint); 190 | 191 | //画椭圆圆弧 192 | //useCenter=false 193 | RectF rect5 = new RectF(startXYPoint.x , startXYPoint.y + 11 * margin + 5 * rectHeight, startXYPoint.x + rectWidth, startXYPoint.y + 11 * margin + 6 * rectHeight); 194 | changeColorGray(); 195 | canvas.drawRect(rect5, mPaint); 196 | changeColorBlue(); 197 | canvas.drawArc(rect5, 0, 90, false, mPaint); 198 | changeToTextPaint(); 199 | String strOvalArc = "我(蓝色区域)是椭圆圆弧,useCenter=false"; 200 | int ovalArcWidth = (int) mPaint.measureText(strOvalArc); 201 | canvas.drawText(strOvalArc, (rectWidth - ovalArcWidth) / 2 + startXYPoint.x, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 11 * margin + 5 * rectHeight + mTextHeight , mPaint); 202 | 203 | //useCenter=true 204 | RectF rect6 = new RectF(startXYPoint.x , startXYPoint.y + 12 * margin + 6 * rectHeight, startXYPoint.x + rectWidth, startXYPoint.y + 12 * margin + 7 * rectHeight); 205 | mPaint.setColor(Color.GRAY); 206 | canvas.drawRect(rect6, mPaint); 207 | mPaint.setColor(Color.BLUE); 208 | canvas.drawArc(rect6, 0, 90, true, mPaint); 209 | changeToTextPaint(); 210 | String strOvalArc1 = "我(蓝色区域)是椭圆圆弧,useCenter=true"; 211 | int ovalArcWidth1 = (int) mPaint.measureText(strOvalArc1); 212 | canvas.drawText(strOvalArc1, (rectWidth - ovalArcWidth1) / 2 + startXYPoint.x , (rectHeight - mTextHeight) / 2 + startXYPoint.y + 12 * margin + 6 * rectHeight + mTextHeight , mPaint); 213 | 214 | //画圆的圆弧 215 | //useCenter=true 216 | RectF rect7 = new RectF(startXYPoint.x+rectHeight/2, startXYPoint.y + 13 * margin + 7 * rectHeight, startXYPoint.x +rectHeight/2+ rectHeight, startXYPoint.y + 13 * margin + 8 * rectHeight); 217 | changeColorGray(); 218 | canvas.drawRect(rect7, mPaint); 219 | changeColorBlue(); 220 | canvas.drawArc(rect7, 0, 90, true, mPaint); 221 | changeToTextPaint(); 222 | String strCircleArc1 = "我(蓝色区域)是圆的圆弧,useCenter=true"; 223 | int circleArcWidth1 = (int) mPaint.measureText(strCircleArc1); 224 | canvas.drawText(strCircleArc1, (rectHeight - circleArcWidth1) / 2 + startXYPoint.x+rectHeight/2, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 13 * margin + 7 * rectHeight + mTextHeight , mPaint); 225 | 226 | //useCenter=false 227 | RectF rect8 = new RectF(startXYPoint.x+rectHeight/2, startXYPoint.y + 14 * margin + 8 * rectHeight, startXYPoint.x+rectHeight/2 + rectHeight, startXYPoint.y + 14 * margin + 9 * rectHeight); 228 | changeColorGray(); 229 | canvas.drawRect(rect8, mPaint); 230 | changeColorBlue(); 231 | canvas.drawArc(rect8, 0, 90, false, mPaint); 232 | changeToTextPaint(); 233 | String strCircleArc = "我(蓝色区域)是圆的圆弧,useCenter=false"; 234 | int circleArcWidth = (int) mPaint.measureText(strCircleArc); 235 | canvas.drawText(strCircleArc, (rectHeight - circleArcWidth) / 2 + startXYPoint.x+rectHeight/2, (rectHeight - mTextHeight) / 2 + startXYPoint.y + 14 * margin + 8 * rectHeight + mTextHeight , mPaint); 236 | 237 | 238 | //画饼状图 239 | RectF rect9 = new RectF(startXYPoint.x+rectHeight/2, startXYPoint.y + 15 * margin + 9 * rectHeight, startXYPoint.x +rectHeight/2+ rectHeight, startXYPoint.y + 15 * margin + 10 * rectHeight); 240 | changeColorGray(); 241 | canvas.drawArc(rect9,0,60,true,mPaint); 242 | changeColorBlue(); 243 | canvas.drawArc(rect9,60,90,true,mPaint); 244 | changeColorRed(); 245 | canvas.drawArc(rect9,90,150,true,mPaint); 246 | mPaint.setColor(Color.BLACK); 247 | canvas.drawArc(rect9,240,120,true,mPaint); 248 | 249 | //画饼状图 250 | RectF rect10 = new RectF(startXYPoint.x+rectHeight/2, startXYPoint.y + 16 * margin + 10 * rectHeight, startXYPoint.x +rectHeight/2+ rectHeight, startXYPoint.y + 16 * margin + 11 * rectHeight); 251 | changeColorGray(); 252 | canvas.drawArc(rect10,0,90,false,mPaint); 253 | changeColorBlue(); 254 | canvas.drawArc(rect10,90,90,false,mPaint); 255 | changeColorRed(); 256 | canvas.drawArc(rect10,180,90,false,mPaint); 257 | mPaint.setColor(Color.BLACK); 258 | canvas.drawArc(rect10,270,90,false,mPaint); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/basic_operation/CustomCoordinateSystem.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.basic_operation; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | 9 | import com.zyyoona7.customviewsets.utils.DensityUtils; 10 | 11 | /** 12 | * Created by zyyoona7 on 2016/8/9. 13 | * 14 | * 自定义坐标系 15 | */ 16 | 17 | public class CustomCoordinateSystem extends View { 18 | private static final int LINE_WITH=5; 19 | private static final int LINE_COLOR=0xffff0000; 20 | private static final int TEXT_SIZE=16; 21 | //坐标系类型 22 | //数学坐标系 23 | private static final int TYPE_MATH=0; 24 | //手机坐标系 25 | private static final int TYPE_PHONE=1; 26 | 27 | //宽 28 | private int mWith=0; 29 | //高 30 | private int mHeight=0; 31 | 32 | //画笔对象 33 | private Paint mPaint; 34 | 35 | //和线的间距 36 | private int mMarginLine=15; 37 | 38 | //和定点的间距 39 | private int mMarginPoint=30; 40 | 41 | private int padding=0; 42 | 43 | private int mType=1; 44 | 45 | public CustomCoordinateSystem(Context context) { 46 | super(context); 47 | initPaint(); 48 | } 49 | 50 | public CustomCoordinateSystem(Context context, AttributeSet attrs) { 51 | super(context, attrs); 52 | initPaint(); 53 | mMarginLine=DensityUtils.dip2px(context,8); 54 | mMarginPoint=DensityUtils.dip2px(context,16); 55 | padding=DensityUtils.dip2px(context,5); 56 | } 57 | 58 | private void initPaint(){ 59 | mPaint=new Paint(); 60 | //设置是否使用抗锯齿功能 61 | mPaint.setAntiAlias(true); 62 | mPaint.setStyle(Paint.Style.STROKE); 63 | mPaint.setStrokeWidth(LINE_WITH); 64 | mPaint.setColor(LINE_COLOR); 65 | //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式Cap.ROUND,或方形样式Cap.SQUARE 66 | mPaint.setStrokeCap(Paint.Cap.ROUND); 67 | 68 | 69 | } 70 | 71 | @Override 72 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 73 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 74 | } 75 | 76 | @Override 77 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 78 | super.onSizeChanged(w, h, oldw, oldh); 79 | mWith=w; 80 | mHeight=h; 81 | } 82 | 83 | @Override 84 | protected void onDraw(Canvas canvas) { 85 | canvas.save(); 86 | if(mType==TYPE_MATH) { 87 | //画x轴的线 88 | canvas.drawLine(0, mHeight / 2, mWith - LINE_WITH, mHeight / 2, mPaint); 89 | //画x轴的上箭头线 90 | canvas.drawLine(mWith - mMarginPoint, mHeight / 2 - mMarginLine, mWith - LINE_WITH, mHeight / 2, mPaint); 91 | //画x轴的下箭头线 92 | canvas.drawLine(mWith - mMarginPoint, mHeight / 2 + mMarginLine, mWith - LINE_WITH, mHeight / 2, mPaint); 93 | //画y轴的线 94 | canvas.drawLine(mWith / 2, 0, mWith / 2, mHeight - LINE_WITH, mPaint); 95 | //画y轴的左箭头线 96 | canvas.drawLine(mWith / 2 - mMarginLine, mHeight - mMarginPoint, mWith / 2, mHeight - LINE_WITH, mPaint); 97 | //画y轴的右箭头线 98 | canvas.drawLine(mWith / 2 + mMarginLine, mHeight - mMarginPoint, mWith / 2, mHeight - LINE_WITH, mPaint); 99 | 100 | mPaint.setStyle(Paint.Style.FILL); 101 | mPaint.setTextSize(DensityUtils.sp2px(getContext(),TEXT_SIZE)); 102 | mPaint.setColor(LINE_COLOR); 103 | //画坐标 104 | //原点坐标 105 | String oCoordinate = "(0,0)"; 106 | //x轴最大坐标 107 | String xCoordinate = "(" + mWith/2 + ",0)"; 108 | //y轴最大坐标 109 | String yCoordinate = "(0," + mHeight/2 + ")"; 110 | //计算原点坐标文字的宽度 111 | float oPointWidth = mPaint.measureText(oCoordinate); 112 | //画原点坐标 113 | canvas.drawText(oCoordinate, mWith / 2 - padding - oPointWidth, mHeight / 2 + DensityUtils.sp2px(getContext(), TEXT_SIZE)+10, mPaint); 114 | //计算x轴坐标的宽度 115 | float xPointWidth = mPaint.measureText(xCoordinate); 116 | //画x轴坐标 117 | canvas.drawText(xCoordinate, mWith - padding - xPointWidth, mHeight / 2 + DensityUtils.sp2px(getContext(), TEXT_SIZE) + mMarginLine, mPaint); 118 | //画y轴坐标 119 | canvas.drawText(yCoordinate, mWith / 2 + mMarginLine, mHeight - padding, mPaint); 120 | }else { 121 | //画x轴的线 122 | canvas.drawLine(padding+mMarginLine,padding+mMarginLine,mWith-LINE_WITH,padding+mMarginLine,mPaint); 123 | //画x轴的上箭头线 124 | canvas.drawLine(mWith-mMarginPoint,padding,mWith-LINE_WITH,padding+mMarginLine,mPaint); 125 | //画x轴的下箭头线 126 | canvas.drawLine(mWith-mMarginPoint,padding+2*mMarginLine,mWith-LINE_WITH,padding+mMarginLine,mPaint); 127 | //画y轴的线 128 | canvas.drawLine(padding+mMarginLine,padding+mMarginLine,padding+mMarginLine,mHeight-LINE_WITH,mPaint); 129 | //画y轴的左箭头线 130 | canvas.drawLine(padding,mHeight-mMarginPoint,padding+mMarginLine,mHeight-LINE_WITH,mPaint); 131 | //画y轴的右箭头线 132 | canvas.drawLine(padding+2*mMarginLine,mHeight-mMarginPoint,padding+mMarginLine,mHeight-LINE_WITH,mPaint); 133 | 134 | mPaint.setStyle(Paint.Style.FILL); 135 | mPaint.setTextSize(DensityUtils.sp2px(getContext(),TEXT_SIZE)); 136 | mPaint.setColor(LINE_COLOR); 137 | //画坐标 138 | //原点坐标 139 | String oCoordinate="(0,0)"; 140 | //x轴最大坐标 141 | String xCoordinate="("+mWith+",0)"; 142 | //y轴最大坐标 143 | String yCoordinate="(0,"+mHeight+")"; 144 | //画原点坐标 145 | //drawText的y轴坐标的基线为底部,所以要减去字体的高度 146 | canvas.drawText(oCoordinate,2*padding+mMarginLine,2*padding+mMarginLine+DensityUtils.sp2px(getContext(),TEXT_SIZE),mPaint); 147 | //计算x轴坐标的宽度 148 | float xPointWidth=mPaint.measureText(xCoordinate); 149 | //画x轴坐标 150 | canvas.drawText(xCoordinate,mWith-padding-xPointWidth,2*padding+2*mMarginLine+DensityUtils.sp2px(getContext(),TEXT_SIZE),mPaint); 151 | //画y轴坐标 152 | canvas.drawText(yCoordinate,2*padding+2*mMarginLine,mHeight-padding,mPaint); 153 | 154 | } 155 | canvas.restore(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/cover_flow/Collector.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.cover_flow; 2 | 3 | import android.view.View; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class Collector 8 | { 9 | ArrayList mOldViews = new ArrayList(); 10 | 11 | protected void collect(View v) 12 | { 13 | this.mOldViews.add(v); 14 | } 15 | 16 | protected View retrieve() 17 | { 18 | if (this.mOldViews.size() == 0) { 19 | return null; 20 | } 21 | return (View)this.mOldViews.remove(0); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/cover_flow/CoverFlowActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.cover_flow; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.BaseAdapter; 7 | import android.widget.ImageView; 8 | 9 | import com.zyyoona7.customviewsets.BaseActivity; 10 | import com.zyyoona7.customviewsets.R; 11 | 12 | import butterknife.BindView; 13 | 14 | public class CoverFlowActivity extends BaseActivity { 15 | 16 | @BindView(R.id.horizontal_carousel) 17 | HorizontalCarouselLayout layout; 18 | 19 | @Override 20 | protected int getContentViewID() { 21 | return R.layout.activity_cover_flow; 22 | } 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | layout.setAdapter(new ImageAdapter()); 28 | layout.setStyle(new HorizontalCarouselStyle(this,HorizontalCarouselStyle.NO_STYLE)); 29 | } 30 | 31 | public class ImageAdapter extends BaseAdapter{ 32 | 33 | @Override 34 | public int getCount() { 35 | return 4; 36 | } 37 | 38 | @Override 39 | public Object getItem(int position) { 40 | return null; 41 | } 42 | 43 | @Override 44 | public long getItemId(int position) { 45 | return 0; 46 | } 47 | 48 | @Override 49 | public View getView(int position, View convertView, ViewGroup parent) { 50 | 51 | ImageView imageView=new ImageView(CoverFlowActivity.this); 52 | imageView.setImageResource(R.mipmap.yoona); 53 | 54 | return imageView; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/cover_flow/HorizontalCarouselLayout.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.cover_flow; 2 | 3 | import android.content.Context; 4 | import android.graphics.Camera; 5 | import android.graphics.Matrix; 6 | import android.os.SystemClock; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.GestureDetector; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.animation.Transformation; 14 | import android.widget.BaseAdapter; 15 | 16 | public class HorizontalCarouselLayout 17 | extends ViewGroup { 18 | private final float SCALE_RATIO = 0.9F; 19 | private int mGestureSensitivity = 80; 20 | private int DURATION = 200; 21 | private int mSpaceBetweenViews = 20; 22 | private int mRotation; 23 | private boolean mRotationEnabled = false; 24 | private int mTranslate; 25 | private boolean mTranslatateEnbabled = false; 26 | private float mSetInactiveViewTransparency = 1.0F; 27 | private int mHowManyViews = 99; 28 | private float mChildSizeRatio = 0.6F; 29 | private BaseAdapter mAdapter = null; 30 | private int mCurrentItem = 0; 31 | private int mCenterView = 0; 32 | private int mChildrenWidth; 33 | private int mChildrenWidthMiddle; 34 | private int mChildrenHeight; 35 | private int mChildrenHeightMiddle; 36 | private int mHeightCenter; 37 | private int mWidthCenter; 38 | private int mMaxChildUnderCenter; 39 | private float mViewZoomOutFactor = 0.0F; 40 | private int mCoverflowRotation = 0; 41 | private Collector mCollector = new Collector(); 42 | private Matrix mMatrix = new Matrix(); 43 | private Context mContext; 44 | private float mGap; 45 | private boolean mIsAnimating = false; 46 | private long mCurTime; 47 | private long mStartTime; 48 | private int mItemtoReach = 0; 49 | private CarouselInterface mCallback; 50 | private Runnable animationTask = new Runnable() { 51 | public void run() { 52 | HorizontalCarouselLayout.this.mCurTime = SystemClock.uptimeMillis(); 53 | long totalTime = HorizontalCarouselLayout.this.mCurTime - HorizontalCarouselLayout.this.mStartTime; 54 | if (totalTime > HorizontalCarouselLayout.this.DURATION) { 55 | if (HorizontalCarouselLayout.this.mItemtoReach > HorizontalCarouselLayout.this.mCurrentItem) { 56 | HorizontalCarouselLayout.this.fillBottom(); 57 | } else { 58 | HorizontalCarouselLayout.this.fillTop(); 59 | } 60 | HorizontalCarouselLayout.this.mCurrentItem = HorizontalCarouselLayout.this.mItemtoReach; 61 | HorizontalCarouselLayout.this.mGap = 0.0F; 62 | HorizontalCarouselLayout.this.mIsAnimating = false; 63 | 64 | HorizontalCarouselLayout.this.mCenterView = HorizontalCarouselLayout.this.mCurrentItem; 65 | if (HorizontalCarouselLayout.this.mCurrentItem >= HorizontalCarouselLayout.this.mMaxChildUnderCenter) { 66 | HorizontalCarouselLayout.this.mCenterView = HorizontalCarouselLayout.this.mMaxChildUnderCenter; 67 | } 68 | HorizontalCarouselLayout.this.removeCallbacks(HorizontalCarouselLayout.this.animationTask); 69 | if (HorizontalCarouselLayout.this.mCallback != null) { 70 | HorizontalCarouselLayout.this.mCallback.onItemChangedListener(mAdapter.getView(mCurrentItem, null, HorizontalCarouselLayout.this), mCurrentItem); 71 | } 72 | } else { 73 | float perCent = (float) totalTime / HorizontalCarouselLayout.this.DURATION; 74 | HorizontalCarouselLayout.this.mGap = ((HorizontalCarouselLayout.this.mCurrentItem - HorizontalCarouselLayout.this.mItemtoReach) * perCent); 75 | HorizontalCarouselLayout.this.post(this); 76 | } 77 | HorizontalCarouselLayout.this.childrenLayout(HorizontalCarouselLayout.this.mGap); 78 | HorizontalCarouselLayout.this.invalidate(); 79 | } 80 | }; 81 | private GestureDetector mGestureDetector = new GestureDetector(this.mContext, 82 | new GestureDetector.SimpleOnGestureListener() { 83 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 84 | if ((!HorizontalCarouselLayout.this.mIsAnimating) && (HorizontalCarouselLayout.this.mAdapter != null)) { 85 | int dx = (int) (e2.getX() - e1.getX()); 86 | if ((Math.abs(dx) > HorizontalCarouselLayout.this.mGestureSensitivity) && 87 | (Math.abs(velocityY) < Math.abs(velocityX))) { 88 | if (velocityX > 0.0F) { 89 | if (HorizontalCarouselLayout.this.mCurrentItem > 0) { 90 | HorizontalCarouselLayout.this.mItemtoReach = (HorizontalCarouselLayout.this.mCurrentItem - 1); 91 | HorizontalCarouselLayout.this.mStartTime = SystemClock.uptimeMillis(); 92 | HorizontalCarouselLayout.this.mIsAnimating = true; 93 | HorizontalCarouselLayout.this.post(HorizontalCarouselLayout.this.animationTask); 94 | return true; 95 | } 96 | } else if (HorizontalCarouselLayout.this.mCurrentItem < HorizontalCarouselLayout.this.mAdapter.getCount() - 1) { 97 | HorizontalCarouselLayout.this.mItemtoReach = (HorizontalCarouselLayout.this.mCurrentItem + 1); 98 | HorizontalCarouselLayout.this.mStartTime = SystemClock.uptimeMillis(); 99 | HorizontalCarouselLayout.this.mIsAnimating = true; 100 | HorizontalCarouselLayout.this.post(HorizontalCarouselLayout.this.animationTask); 101 | return true; 102 | } 103 | } 104 | } 105 | return false; 106 | } 107 | } 108 | ); 109 | 110 | public HorizontalCarouselLayout(Context context) { 111 | super(context); 112 | this.mContext = context; 113 | initSlidingAnimation(); 114 | } 115 | 116 | public HorizontalCarouselLayout(Context context, AttributeSet attrs) { 117 | super(context, attrs); 118 | this.mContext = context; 119 | initSlidingAnimation(); 120 | } 121 | 122 | public HorizontalCarouselLayout(Context context, AttributeSet attrs, int defStyle) { 123 | super(context, attrs, defStyle); 124 | this.mContext = context; 125 | initSlidingAnimation(); 126 | } 127 | 128 | public void disableRotation() { 129 | this.mRotationEnabled = false; 130 | } 131 | 132 | public void disableTranslate() { 133 | this.mTranslatateEnbabled = false; 134 | } 135 | 136 | public void setOnCarouselViewChangedListener(CarouselInterface carouselInterface) { 137 | this.mCallback = carouselInterface; 138 | } 139 | 140 | public void setGestureSensitivity(int gestureSensitivity) { 141 | this.mGestureSensitivity = gestureSensitivity; 142 | } 143 | 144 | public void setStyle(HorizontalCarouselStyle style) { 145 | this.mSetInactiveViewTransparency = style.getInactiveViewTransparency(); 146 | this.mSpaceBetweenViews = style.getSpaceBetweenViews(); 147 | this.mRotation = style.getRotation(); 148 | this.mRotationEnabled = style.isRotationEnabled(); 149 | this.mTranslate = style.getTranslate(); 150 | this.mTranslatateEnbabled = style.isTranslatateEnbabled(); 151 | this.mHowManyViews = style.getHowManyViews(); 152 | this.mChildSizeRatio = style.getChildSizeRatio(); 153 | this.mCoverflowRotation = style.getCoverflowRotation(); 154 | this.mViewZoomOutFactor = style.getViewZoomOutFactor(); 155 | this.DURATION = style.getAnimationTime(); 156 | } 157 | 158 | public void setAdapter(BaseAdapter adapter) { 159 | if (adapter != null) { 160 | this.mAdapter = adapter; 161 | this.mCenterView = (this.mCurrentItem = 0); 162 | if (this.mHowManyViews % 2 == 0) { 163 | this.mMaxChildUnderCenter = (this.mHowManyViews / 2); 164 | } else { 165 | this.mMaxChildUnderCenter = (this.mHowManyViews / 2); 166 | } 167 | for (int i = 0; i <= this.mMaxChildUnderCenter; i++) { 168 | if (i > this.mAdapter.getCount() - 1) { 169 | break; 170 | } 171 | View v = this.mAdapter.getView(i, null, this); 172 | addView(v); 173 | } 174 | childrenLayout(0.0F); 175 | invalidate(); 176 | } 177 | } 178 | 179 | private void fillTop() { 180 | if ((this.mCenterView < this.mMaxChildUnderCenter) && 181 | (getChildCount() > this.mMaxChildUnderCenter + 1)) { 182 | View old = getChildAt(getChildCount() - 1); 183 | detachViewFromParent(old); 184 | this.mCollector.collect(old); 185 | } 186 | if (getChildCount() >= this.mHowManyViews) { 187 | View old = getChildAt(this.mHowManyViews - 1); 188 | detachViewFromParent(old); 189 | this.mCollector.collect(old); 190 | } 191 | int indexToRequest = this.mCurrentItem - (this.mMaxChildUnderCenter + 1); 192 | if (indexToRequest >= 0) { 193 | Log.v("UITEST", "Fill top with " + indexToRequest); 194 | View recycled = this.mCollector.retrieve(); 195 | View v = this.mAdapter.getView(indexToRequest, recycled, this); 196 | if (recycled != null) { 197 | attachViewToParent(v, 0, generateDefaultLayoutParams()); 198 | v.measure(this.mChildrenWidth, this.mChildrenHeight); 199 | } else { 200 | addView(v, 0); 201 | } 202 | } 203 | } 204 | 205 | private void fillBottom() { 206 | if (this.mCenterView >= this.mMaxChildUnderCenter) { 207 | View old = getChildAt(0); 208 | detachViewFromParent(old); 209 | this.mCollector.collect(old); 210 | } 211 | if (getChildCount() >= this.mHowManyViews) { 212 | View old = getChildAt(0); 213 | detachViewFromParent(old); 214 | this.mCollector.collect(old); 215 | } 216 | int indexToRequest = this.mCurrentItem + (this.mMaxChildUnderCenter + 1); 217 | if (indexToRequest < this.mAdapter.getCount()) { 218 | Log.v("UITEST", "Fill bottom with " + indexToRequest); 219 | View recycled = this.mCollector.retrieve(); 220 | View v = this.mAdapter.getView(indexToRequest, recycled, this); 221 | if (recycled != null) { 222 | Log.v("UITEST", "view attached"); 223 | attachViewToParent(v, -1, generateDefaultLayoutParams()); 224 | v.measure(this.mChildrenWidth, this.mChildrenHeight); 225 | } else { 226 | Log.v("UITEST", "view added"); 227 | addView(v, -1); 228 | } 229 | } 230 | } 231 | 232 | protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 233 | return new ViewGroup.LayoutParams(-1, 234 | -1); 235 | } 236 | 237 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 238 | return new ViewGroup.LayoutParams(p); 239 | } 240 | 241 | private void initSlidingAnimation() { 242 | setChildrenDrawingOrderEnabled(true); 243 | setStaticTransformationsEnabled(true); 244 | setOnTouchListener(new View.OnTouchListener() { 245 | public boolean onTouch(View v, MotionEvent event) { 246 | HorizontalCarouselLayout.this.mGestureDetector.onTouchEvent(event); 247 | return true; 248 | } 249 | }); 250 | } 251 | 252 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 253 | int count = getChildCount(); 254 | int specWidthSize = View.MeasureSpec.getSize(widthMeasureSpec); 255 | int specHeightSize = View.MeasureSpec.getSize(heightMeasureSpec); 256 | this.mWidthCenter = (specWidthSize / 2); 257 | this.mHeightCenter = (specHeightSize / 2); 258 | this.mChildrenWidth = ((int) (specWidthSize * this.mChildSizeRatio)); 259 | this.mChildrenHeight = ((int) (specHeightSize * this.mChildSizeRatio)); 260 | this.mChildrenWidthMiddle = (this.mChildrenWidth / 2); 261 | this.mChildrenHeightMiddle = (this.mChildrenHeight / 2); 262 | for (int i = 0; i < count; i++) { 263 | View child = getChildAt(i); 264 | measureChild(child, this.mChildrenWidth, this.mChildrenHeight); 265 | } 266 | setMeasuredDimension(specWidthSize, specHeightSize); 267 | } 268 | 269 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 270 | childrenLayout(0.0F); 271 | } 272 | 273 | private void childrenLayout(float gap) { 274 | int leftCenterView = this.mWidthCenter - this.mChildrenWidth / 2; 275 | int topCenterView = this.mHeightCenter - this.mChildrenHeight / 2; 276 | int count = getChildCount(); 277 | for (int i = 0; i < count; i++) { 278 | View child = getChildAt(i); 279 | float offset = this.mCenterView - i - gap; 280 | int left = (int) (leftCenterView - this.mSpaceBetweenViews * offset); 281 | child.layout(left, topCenterView, left + this.mChildrenWidth, 282 | topCenterView + this.mChildrenHeight); 283 | } 284 | } 285 | 286 | protected int getChildDrawingOrder(int childCount, int i) { 287 | int centerView = this.mCenterView; 288 | if (this.mGap > 0.5F) { 289 | centerView--; 290 | } else if (this.mGap < -0.5F) { 291 | centerView++; 292 | } 293 | if (i < centerView) { 294 | return i; 295 | } 296 | if (i > centerView) { 297 | return centerView + (childCount - 1) - i; 298 | } 299 | return childCount - 1; 300 | } 301 | 302 | protected boolean getChildStaticTransformation(View child, Transformation t) { 303 | Camera camera = new Camera(); 304 | int leftCenterView = this.mWidthCenter - this.mChildrenWidthMiddle; 305 | float offset = (-child.getLeft() + leftCenterView) / 306 | this.mSpaceBetweenViews; 307 | if (offset != 0.0F) { 308 | float absOffset = Math.abs(offset); 309 | float scale = (float) Math.pow(0.8999999761581421D, absOffset); 310 | t.clear(); 311 | t.setTransformationType(Transformation.TYPE_MATRIX); 312 | t.setAlpha(this.mSetInactiveViewTransparency); 313 | Matrix m = t.getMatrix(); 314 | m.setScale(scale, scale); 315 | if (this.mTranslatateEnbabled) { 316 | m.setTranslate(0.0F, this.mTranslate * absOffset); 317 | } 318 | if (offset > 0.0F) { 319 | camera.save(); 320 | camera.translate(0.0F, 0.0F, this.mViewZoomOutFactor * offset); 321 | camera.rotateY(this.mCoverflowRotation); 322 | camera.getMatrix(m); 323 | camera.restore(); 324 | m.preTranslate(-this.mChildrenWidthMiddle, -this.mChildrenHeight); 325 | m.postTranslate(this.mChildrenWidthMiddle, this.mChildrenHeight); 326 | } else { 327 | camera.save(); 328 | camera.translate(0.0F, 0.0F, -(this.mViewZoomOutFactor * offset)); 329 | camera.rotateY(-this.mCoverflowRotation); 330 | camera.getMatrix(m); 331 | camera.restore(); 332 | m.preTranslate(-this.mChildrenWidthMiddle, -this.mChildrenHeight); 333 | m.postTranslate(this.mChildrenWidthMiddle, this.mChildrenHeight); 334 | } 335 | this.mMatrix.reset(); 336 | if (this.mRotationEnabled) { 337 | this.mMatrix.setRotate(this.mRotation * offset); 338 | } 339 | this.mMatrix.preTranslate(-this.mChildrenWidthMiddle, -this.mChildrenHeightMiddle); 340 | this.mMatrix.postTranslate(this.mChildrenWidthMiddle, this.mChildrenHeightMiddle); 341 | m.setConcat(m, this.mMatrix); 342 | } 343 | return true; 344 | } 345 | 346 | public static abstract interface CarouselInterface { 347 | public abstract void onItemChangedListener(View paramView, int paramInt); 348 | } 349 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/cover_flow/HorizontalCarouselStyle.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.cover_flow; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | public class HorizontalCarouselStyle 7 | { 8 | public static final int NO_STYLE = 0; 9 | public static final int STYLE_ZOOMED_OUT = 1; 10 | public static final int STYLE_COVERFLOW = 2; 11 | public static final int STYLE_RIGHT_ALIGNED_ROTATION = 3; 12 | public static final int STYLE_LEFT_ALIGNED_ROTATION = 4; 13 | private int mCoverflowRotation; 14 | private float mViewZoomOutFactor; 15 | private float mInactiveViewTransparency; 16 | private int mSpaceBetweenViews; 17 | private int mRotation; 18 | private boolean mRotationEnabled; 19 | private int mTranslate; 20 | private boolean mTranslatateEnbabled; 21 | private int mHowManyViews; 22 | private float mChildSizeRatio; 23 | private int mAnimationTime; 24 | private int dip; 25 | 26 | public HorizontalCarouselStyle(Context context, int style) 27 | { 28 | this.dip = ((int) TypedValue.applyDimension(1, 1.0F, context.getResources().getDisplayMetrics())); 29 | this.mInactiveViewTransparency = 1.0F; 30 | this.mHowManyViews = 99; 31 | this.mChildSizeRatio = 0.6F; 32 | this.mAnimationTime = 200; 33 | setStyle(style); 34 | } 35 | 36 | private void setStyle(int style) 37 | { 38 | switch (style) 39 | { 40 | case 1: 41 | this.mSpaceBetweenViews = (80 * this.dip); 42 | this.mRotation = 0; 43 | this.mRotationEnabled = false; 44 | this.mTranslate = 50; 45 | this.mCoverflowRotation = 0; 46 | this.mViewZoomOutFactor = 30.0F; 47 | this.mTranslatateEnbabled = true; 48 | break; 49 | case 2: 50 | this.mSpaceBetweenViews = (80 * this.dip); 51 | this.mRotation = 0; 52 | this.mRotationEnabled = false; 53 | this.mTranslate = -50; 54 | this.mCoverflowRotation = 45; 55 | this.mViewZoomOutFactor = 60.0F; 56 | this.mTranslatateEnbabled = true; 57 | break; 58 | case 3: 59 | this.mSpaceBetweenViews = (80 * this.dip); 60 | this.mRotation = 30; 61 | this.mRotationEnabled = true; 62 | this.mTranslate = 50; 63 | this.mCoverflowRotation = 0; 64 | this.mViewZoomOutFactor = 0.0F; 65 | this.mTranslatateEnbabled = true; 66 | break; 67 | case 4: 68 | this.mSpaceBetweenViews = (80 * this.dip); 69 | this.mRotation = -30; 70 | this.mRotationEnabled = true; 71 | this.mTranslate = -50; 72 | this.mCoverflowRotation = 0; 73 | this.mViewZoomOutFactor = 0.0F; 74 | this.mTranslatateEnbabled = true; 75 | break; 76 | default: 77 | this.mSpaceBetweenViews = (50 * this.dip); 78 | this.mRotation = 0; 79 | this.mRotationEnabled = false; 80 | this.mTranslate = 0; 81 | this.mCoverflowRotation = 0; 82 | this.mViewZoomOutFactor = 0.0F; 83 | this.mTranslatateEnbabled = false; 84 | } 85 | } 86 | 87 | public void setInactiveViewTransparency(float mSetInactiveViewTransparency) 88 | { 89 | this.mInactiveViewTransparency = mSetInactiveViewTransparency; 90 | } 91 | 92 | public void setSpaceBetweenViews(int spaceInPixel) 93 | { 94 | this.mSpaceBetweenViews = (spaceInPixel * this.dip); 95 | } 96 | 97 | public void setRotation(int rotation) 98 | { 99 | this.mRotationEnabled = true; 100 | this.mRotation = rotation; 101 | } 102 | 103 | public void setTranslate(int translate) 104 | { 105 | this.mTranslatateEnbabled = true; 106 | this.mTranslate = translate; 107 | } 108 | 109 | public boolean setHowManyViews(int howMany) 110 | { 111 | if (howMany % 2 != 0) { 112 | return false; 113 | } 114 | this.mHowManyViews = howMany; 115 | return true; 116 | } 117 | 118 | public boolean setChildSizeRation(float parentPerCent) 119 | { 120 | if ((parentPerCent > 1.0F) && (parentPerCent < 1.0F)) { 121 | return false; 122 | } 123 | this.mChildSizeRatio = parentPerCent; 124 | return true; 125 | } 126 | 127 | public void setAnimationTime(int mAnimationTime) 128 | { 129 | this.mAnimationTime = mAnimationTime; 130 | } 131 | 132 | public void setCoverflowRotation(int mCoverflowRotation) 133 | { 134 | this.mCoverflowRotation = mCoverflowRotation; 135 | } 136 | 137 | public void setViewZoomOutFactor(float mViewZoomOutFactor) 138 | { 139 | this.mViewZoomOutFactor = mViewZoomOutFactor; 140 | } 141 | 142 | public float getInactiveViewTransparency() 143 | { 144 | return this.mInactiveViewTransparency; 145 | } 146 | 147 | public int getSpaceBetweenViews() 148 | { 149 | return this.mSpaceBetweenViews; 150 | } 151 | 152 | public int getRotation() 153 | { 154 | return this.mRotation; 155 | } 156 | 157 | public boolean isRotationEnabled() 158 | { 159 | return this.mRotationEnabled; 160 | } 161 | 162 | public int getTranslate() 163 | { 164 | return this.mTranslate; 165 | } 166 | 167 | public boolean isTranslatateEnbabled() 168 | { 169 | return this.mTranslatateEnbabled; 170 | } 171 | 172 | public int getHowManyViews() 173 | { 174 | return this.mHowManyViews; 175 | } 176 | 177 | public float getChildSizeRatio() 178 | { 179 | return this.mChildSizeRatio; 180 | } 181 | 182 | public int getAnimationTime() 183 | { 184 | return this.mAnimationTime; 185 | } 186 | 187 | public int getCoverflowRotation() 188 | { 189 | return this.mCoverflowRotation; 190 | } 191 | 192 | public float getViewZoomOutFactor() 193 | { 194 | return this.mViewZoomOutFactor; 195 | } 196 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/cover_flow/ScrollerLayout.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.cover_flow; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewConfigurationCompat; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | import android.view.ViewConfiguration; 10 | import android.view.ViewGroup; 11 | import android.widget.Scroller; 12 | 13 | /** 14 | * Created by User on 2016/8/22. 15 | */ 16 | 17 | public class ScrollerLayout extends ViewGroup { 18 | 19 | private static final String TAG = "ScrollerLayout"; 20 | 21 | /** 22 | * 用于完成滚动操作的实例 23 | */ 24 | private Scroller mScroller; 25 | 26 | /** 27 | * 判定为拖动的最小移动像素数 28 | */ 29 | private int mTouchSlop; 30 | 31 | /** 32 | * 手机按下时的屏幕坐标 33 | */ 34 | private float mXDown; 35 | 36 | /** 37 | * 手机当时所处的屏幕坐标 38 | */ 39 | private float mXMove; 40 | 41 | /** 42 | * 上次触发ACTION_MOVE事件时的屏幕坐标 43 | */ 44 | private float mXLastMove; 45 | 46 | /** 47 | * 界面可滚动的左边界 48 | */ 49 | private int leftBorder; 50 | 51 | /** 52 | * 界面可滚动的右边界 53 | */ 54 | private int rightBorder; 55 | 56 | public ScrollerLayout(Context context, AttributeSet attrs) { 57 | super(context, attrs); 58 | // 第一步,创建Scroller的实例 59 | mScroller = new Scroller(context); 60 | ViewConfiguration configuration = ViewConfiguration.get(context); 61 | // 获取TouchSlop值 62 | mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 63 | } 64 | 65 | @Override 66 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 67 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 68 | int childCount = getChildCount(); 69 | for (int i = 0; i < childCount; i++) { 70 | View childView = getChildAt(i); 71 | // 为ScrollerLayout中的每一个子控件测量大小 72 | measureChild(childView, widthMeasureSpec, heightMeasureSpec); 73 | } 74 | } 75 | 76 | @Override 77 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 78 | if (changed) { 79 | int childCount = getChildCount(); 80 | for (int i = 0; i < childCount; i++) { 81 | View childView = getChildAt(i); 82 | // 为ScrollerLayout中的每一个子控件在水平方向上进行布局 83 | childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight()); 84 | } 85 | // 初始化左右边界值 86 | leftBorder = getChildAt(0).getLeft(); 87 | rightBorder = getChildAt(getChildCount() - 1).getRight(); 88 | } 89 | } 90 | 91 | @Override 92 | public boolean onInterceptTouchEvent(MotionEvent ev) { 93 | switch (ev.getAction()) { 94 | case MotionEvent.ACTION_DOWN: 95 | mXDown = ev.getRawX(); 96 | mXLastMove = mXDown; 97 | break; 98 | case MotionEvent.ACTION_MOVE: 99 | mXMove = ev.getRawX(); 100 | float diff = Math.abs(mXMove - mXDown); 101 | mXLastMove = mXMove; 102 | // 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件 103 | if (diff > mTouchSlop) { 104 | return true; 105 | } 106 | break; 107 | } 108 | return super.onInterceptTouchEvent(ev); 109 | } 110 | 111 | @Override 112 | public boolean onTouchEvent(MotionEvent event) { 113 | switch (event.getAction()) { 114 | case MotionEvent.ACTION_MOVE: 115 | mXMove = event.getRawX(); 116 | int scrolledX = (int) (mXLastMove - mXMove); 117 | if (getScrollX() + scrolledX < leftBorder) { 118 | scrollTo(leftBorder, 0); 119 | return true; 120 | } else if (getScrollX() + getWidth() + scrolledX > rightBorder) { 121 | scrollTo(rightBorder - getWidth(), 0); 122 | return true; 123 | } 124 | scrollBy(scrolledX, 0); 125 | mXLastMove = mXMove; 126 | break; 127 | case MotionEvent.ACTION_UP: 128 | // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面 129 | int targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); 130 | int dx = targetIndex * getWidth() - getScrollX(); 131 | // 第二步,调用startScroll()方法来初始化滚动数据并刷新界面 132 | mScroller.startScroll(getScrollX(), 0, dx, 0); 133 | invalidate(); 134 | break; 135 | } 136 | return super.onTouchEvent(event); 137 | } 138 | 139 | @Override 140 | public void computeScroll() { 141 | // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 142 | if (mScroller.computeScrollOffset()) { 143 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 144 | invalidate(); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/custom_layout_manager/CardActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.custom_layout_manager; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.zyyoona7.customviewsets.R; 13 | 14 | import java.util.Random; 15 | 16 | public class CardActivity extends AppCompatActivity { 17 | 18 | private static final int[] COLORS = {0xff00FFFF, 0xffDEB887, 0xff5F9EA0, 19 | 0xff7FFF00, 0xff6495ED, 0xffDC143C, 20 | 0xff008B8B, 0xff006400, 0xff2F4F4F, 21 | 0xffFF69B4, 0xffFF00FF, 0xffCD5C5C, 22 | 0xff90EE90, 0xff87CEFA, 0xff800000}; 23 | 24 | private RecyclerView mRecyclerView; 25 | private Adapter mAdapter = new Adapter(); 26 | 27 | private int mCount = 5000; 28 | private int mGroupSize = 3; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_card); 34 | mRecyclerView = (RecyclerView) findViewById(R.id.list); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | mRecyclerView.setLayoutManager(new CardLayoutManager(mGroupSize, true)); 40 | mRecyclerView.setAdapter(mAdapter); 41 | } 42 | 43 | public void add(View view) { 44 | mCount += 10; 45 | mAdapter.notifyDataSetChanged(); 46 | } 47 | 48 | public void change(View view) { 49 | if (mGroupSize == 3) { 50 | mGroupSize = 9; 51 | } else { 52 | mGroupSize = 3; 53 | } 54 | init(); 55 | } 56 | 57 | class Adapter extends RecyclerView.Adapter { 58 | 59 | @Override 60 | public Holder onCreateViewHolder(ViewGroup parent, int viewType) { 61 | View item = LayoutInflater.from(parent.getContext()) 62 | .inflate(R.layout.item, parent, false); 63 | return new Holder(item); 64 | } 65 | 66 | @Override 67 | public void onBindViewHolder(final Holder holder, final int position) { 68 | // holder.item.setText("" + position); 69 | holder.item.setCardColor(randomColor()); 70 | holder.text.setText("菜单" + position); 71 | holder.itemView.setOnClickListener(new View.OnClickListener() { 72 | @Override 73 | public void onClick(View view) { 74 | Toast.makeText(CardActivity.this, holder.text.getText(), Toast.LENGTH_SHORT).show(); 75 | } 76 | }); 77 | } 78 | 79 | @Override 80 | public int getItemCount() { 81 | return mCount; 82 | } 83 | 84 | private int randomColor() { 85 | return COLORS[new Random().nextInt(COLORS.length)]; 86 | } 87 | 88 | class Holder extends RecyclerView.ViewHolder { 89 | CardItemView item; 90 | TextView text; 91 | 92 | public Holder(View itemView) { 93 | super(itemView); 94 | item = (CardItemView) itemView.findViewById(R.id.item); 95 | text = (TextView) itemView.findViewById(R.id.text); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/custom_layout_manager/CardItemView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.custom_layout_manager; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.graphics.Region; 11 | import android.util.AttributeSet; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | 15 | import com.zyyoona7.customviewsets.R; 16 | 17 | /** 18 | * Created by qibin on 16-9-26. 19 | */ 20 | 21 | public class CardItemView extends View { 22 | 23 | private int mSize; 24 | private Paint mPaint; 25 | private Path mDrawPath; 26 | private Region mRegion; 27 | 28 | public CardItemView(Context context) { 29 | this(context, null, 0); 30 | } 31 | 32 | public CardItemView(Context context, AttributeSet attrs) { 33 | this(context, attrs, 0); 34 | } 35 | 36 | public CardItemView(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 39 | mPaint.setStyle(Paint.Style.FILL); 40 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Card, defStyleAttr, 0); 41 | mSize = ta.getDimensionPixelSize(R.styleable.Card_size, 10); 42 | mPaint.setColor(ta.getColor(R.styleable.Card_bgColor, 0)); 43 | ta.recycle(); 44 | 45 | mRegion = new Region(); 46 | mDrawPath = new Path(); 47 | 48 | mDrawPath.moveTo(0, mSize / 2); 49 | mDrawPath.lineTo(mSize / 2, 0); 50 | mDrawPath.lineTo(mSize, mSize / 2); 51 | mDrawPath.lineTo(mSize / 2, mSize); 52 | mDrawPath.close(); 53 | } 54 | 55 | @Override 56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 57 | setMeasuredDimension(mSize, mSize); 58 | } 59 | 60 | @Override 61 | public boolean dispatchTouchEvent(MotionEvent event) { 62 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 63 | if (!isEventInPath(event)) { return false;} 64 | } 65 | 66 | return super.dispatchTouchEvent(event); 67 | } 68 | 69 | private boolean isEventInPath(MotionEvent event) { 70 | RectF bounds = new RectF(); 71 | mDrawPath.computeBounds(bounds, true); 72 | mRegion.setPath(mDrawPath, new Region((int)bounds.left, 73 | (int)bounds.top, (int)bounds.right, (int)bounds.bottom)); 74 | return mRegion.contains((int) event.getX(), (int) event.getY()); 75 | } 76 | 77 | @Override 78 | protected void onDraw(Canvas canvas) { 79 | canvas.drawColor(Color.TRANSPARENT); 80 | canvas.drawPath(mDrawPath, mPaint); 81 | } 82 | 83 | public void setCardColor(int color) { 84 | mPaint.setColor(color); 85 | invalidate(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/custom_layout_manager/CardLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.custom_layout_manager; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | /** 9 | * Created by qibin on 16-9-25. 10 | */ 11 | 12 | public class CardLayoutManager extends RecyclerView.LayoutManager { 13 | public static final int DEFAULT_GROUP_SIZE = 5; 14 | 15 | private int mGroupSize; 16 | private int mHorizontalOffset; 17 | private int mVerticalOffset; 18 | private int mTotalWidth; 19 | private int mTotalHeight; 20 | private int mGravityOffset; 21 | private boolean isGravityCenter; 22 | 23 | private Pool mItemFrames; 24 | 25 | public CardLayoutManager(boolean center) { 26 | this(DEFAULT_GROUP_SIZE, true); 27 | } 28 | 29 | public CardLayoutManager(int groupSize, boolean center) { 30 | mGroupSize = groupSize; 31 | isGravityCenter = center; 32 | mItemFrames = new Pool<>(new Pool.New() { 33 | @Override 34 | public Rect get() { return new Rect();} 35 | }); 36 | } 37 | 38 | @Override 39 | public RecyclerView.LayoutParams generateDefaultLayoutParams() { 40 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 41 | ViewGroup.LayoutParams.WRAP_CONTENT); 42 | } 43 | 44 | @Override 45 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 46 | if (getItemCount() <= 0 || state.isPreLayout()) { return;} 47 | 48 | detachAndScrapAttachedViews(recycler); 49 | View first = recycler.getViewForPosition(0); 50 | measureChildWithMargins(first, 0, 0); 51 | int itemWidth = getDecoratedMeasuredWidth(first); 52 | int itemHeight = getDecoratedMeasuredHeight(first); 53 | 54 | int firstLineSize = mGroupSize / 2 + 1; 55 | int secondLineSize = firstLineSize + mGroupSize / 2; 56 | if (isGravityCenter && firstLineSize * itemWidth < getHorizontalSpace()) { 57 | mGravityOffset = (getHorizontalSpace() - firstLineSize * itemWidth) / 2; 58 | } else { 59 | mGravityOffset = 0; 60 | } 61 | 62 | for (int i = 0; i < getItemCount(); i++) { 63 | Rect item = mItemFrames.get(i); 64 | float coefficient = isFirstGroup(i) ? 1.5f : 1.f; 65 | int offsetHeight = (int) ((i / mGroupSize) * itemHeight * coefficient); 66 | 67 | // 每一组的第一行 68 | if (isItemInFirstLine(i)) { 69 | int offsetInLine = i < firstLineSize ? i : i % mGroupSize; 70 | item.set(mGravityOffset + offsetInLine * itemWidth, offsetHeight, mGravityOffset + offsetInLine * itemWidth + itemWidth, 71 | itemHeight + offsetHeight); 72 | }else { 73 | int lineOffset = itemHeight / 2; 74 | int offsetInLine = (i < secondLineSize ? i : i % mGroupSize) - firstLineSize; 75 | item.set(mGravityOffset + offsetInLine * itemWidth + itemWidth / 2, 76 | offsetHeight + lineOffset, mGravityOffset + offsetInLine * itemWidth + itemWidth + itemWidth / 2, 77 | itemHeight + offsetHeight + lineOffset); 78 | } 79 | } 80 | 81 | mTotalWidth = Math.max(firstLineSize * itemWidth, getHorizontalSpace()); 82 | int totalHeight = getGroupSize() * itemHeight; 83 | if (!isItemInFirstLine(getItemCount() - 1)) { totalHeight += itemHeight / 2;} 84 | mTotalHeight = Math.max(totalHeight, getVerticalSpace()); 85 | fill(recycler, state); 86 | } 87 | 88 | private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) { 89 | if (getItemCount() <= 0 || state.isPreLayout()) { return;} 90 | Rect displayRect = new Rect(mHorizontalOffset, mVerticalOffset, 91 | getHorizontalSpace() + mHorizontalOffset, 92 | getVerticalSpace() + mVerticalOffset); 93 | 94 | Rect rect = new Rect(); 95 | for (int i = 0; i < getChildCount(); i++) { 96 | View item = getChildAt(i); 97 | rect.left = getDecoratedLeft(item); 98 | rect.top = getDecoratedTop(item); 99 | rect.right = getDecoratedRight(item); 100 | rect.bottom = getDecoratedBottom(item); 101 | if (!Rect.intersects(displayRect, rect)) { 102 | removeAndRecycleView(item, recycler); 103 | } 104 | } 105 | 106 | for (int i = 0; i < getItemCount(); i++) { 107 | Rect frame = mItemFrames.get(i); 108 | if (Rect.intersects(displayRect, frame)) { 109 | View scrap = recycler.getViewForPosition(i); 110 | addView(scrap); 111 | measureChildWithMargins(scrap, 0, 0); 112 | layoutDecorated(scrap, frame.left - mHorizontalOffset, frame.top - mVerticalOffset, 113 | frame.right - mHorizontalOffset, frame.bottom - mVerticalOffset); 114 | } 115 | } 116 | } 117 | 118 | @Override 119 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { 120 | detachAndScrapAttachedViews(recycler); 121 | if (mVerticalOffset + dy < 0) { 122 | dy = -mVerticalOffset; 123 | } else if (mVerticalOffset + dy > mTotalHeight - getVerticalSpace()) { 124 | dy = mTotalHeight - getVerticalSpace() - mVerticalOffset; 125 | } 126 | 127 | offsetChildrenVertical(-dy); 128 | fill(recycler, state); 129 | mVerticalOffset += dy; 130 | return dy; 131 | } 132 | 133 | @Override 134 | public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { 135 | detachAndScrapAttachedViews(recycler); 136 | if (mHorizontalOffset + dx < 0) { 137 | dx = -mHorizontalOffset; 138 | } else if (mHorizontalOffset + dx > mTotalWidth - getHorizontalSpace()) { 139 | dx = mTotalWidth - getHorizontalSpace() - mHorizontalOffset; 140 | } 141 | 142 | offsetChildrenHorizontal(-dx); 143 | fill(recycler, state); 144 | mHorizontalOffset += dx; 145 | return dx; 146 | } 147 | 148 | @Override 149 | public boolean canScrollVertically() { 150 | return true; 151 | } 152 | 153 | @Override 154 | public boolean canScrollHorizontally() { 155 | return true; 156 | } 157 | 158 | private boolean isItemInFirstLine(int index) { 159 | int firstLineSize = mGroupSize / 2 + 1; 160 | return index < firstLineSize || (index >= mGroupSize && index % mGroupSize < firstLineSize); 161 | } 162 | 163 | private int getGroupSize() { 164 | return (int) Math.ceil(getItemCount() / (float)mGroupSize); 165 | } 166 | 167 | private boolean isFirstGroup(int index) { 168 | return index < mGroupSize; 169 | } 170 | 171 | private int getHorizontalSpace() { 172 | return getWidth() - getPaddingLeft() - getPaddingRight(); 173 | } 174 | 175 | private int getVerticalSpace() { 176 | return getHeight() - getPaddingTop() - getPaddingBottom(); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/custom_layout_manager/Pool.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.custom_layout_manager; 2 | 3 | import android.support.v4.util.SparseArrayCompat; 4 | 5 | /** 6 | * Created by qibin on 16-9-25. 7 | */ 8 | 9 | public class Pool { 10 | private SparseArrayCompat mPool; 11 | private New mNewInstance; 12 | 13 | public Pool(New newInstance) { 14 | mPool = new SparseArrayCompat<>(); 15 | mNewInstance = newInstance; 16 | } 17 | 18 | public T get(int key) { 19 | T res = mPool.get(key); 20 | if (res == null) { 21 | res = mNewInstance.get(); 22 | mPool.put(key, res); 23 | } 24 | return res; 25 | } 26 | 27 | public interface New { 28 | T get(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/heart/Heart.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.heart; 2 | 3 | /** 4 | * Created by zyyoona7 on 2017/7/4. 5 | */ 6 | 7 | public class Heart { 8 | 9 | public static final int DEFAULT = 0; 10 | public static final int PINK = 1; 11 | public static final int CYAN = 2; 12 | public static final int GREEN = 3; 13 | public static final int YELLOW = 4; 14 | public static final int BLUE = 5; 15 | 16 | //实时坐标 17 | private float x; 18 | private float y; 19 | 20 | //起始点坐标 21 | private float startX; 22 | private float startY; 23 | 24 | //结束点坐标 25 | private float endX; 26 | private float endY; 27 | 28 | //三阶贝塞尔曲线(两个控制点) 29 | //控制点1坐标 30 | private float control1X; 31 | private float control1Y; 32 | 33 | //控制点2坐标 34 | private float control2X; 35 | private float control2Y; 36 | 37 | //实时的时间 38 | private float t=0; 39 | //速率 40 | private float speed; 41 | 42 | //bitmap图片类型 43 | private int type=DEFAULT; 44 | 45 | public float getX() { 46 | return x; 47 | } 48 | 49 | public void setX(float x) { 50 | this.x = x; 51 | } 52 | 53 | public float getY() { 54 | return y; 55 | } 56 | 57 | public void setY(float y) { 58 | this.y = y; 59 | } 60 | 61 | public float getStartX() { 62 | return startX; 63 | } 64 | 65 | public void setStartX(float startX) { 66 | this.startX = startX; 67 | } 68 | 69 | public float getStartY() { 70 | return startY; 71 | } 72 | 73 | public void setStartY(float startY) { 74 | this.startY = startY; 75 | } 76 | 77 | public float getEndX() { 78 | return endX; 79 | } 80 | 81 | public void setEndX(float endX) { 82 | this.endX = endX; 83 | } 84 | 85 | public float getEndY() { 86 | return endY; 87 | } 88 | 89 | public void setEndY(float endY) { 90 | this.endY = endY; 91 | } 92 | 93 | public float getControl1X() { 94 | return control1X; 95 | } 96 | 97 | public void setControl1X(float control1X) { 98 | this.control1X = control1X; 99 | } 100 | 101 | public float getControl1Y() { 102 | return control1Y; 103 | } 104 | 105 | public void setControl1Y(float control1Y) { 106 | this.control1Y = control1Y; 107 | } 108 | 109 | public float getControl2X() { 110 | return control2X; 111 | } 112 | 113 | public void setControl2X(float control2X) { 114 | this.control2X = control2X; 115 | } 116 | 117 | public float getControl2Y() { 118 | return control2Y; 119 | } 120 | 121 | public void setControl2Y(float control2Y) { 122 | this.control2Y = control2Y; 123 | } 124 | 125 | public float getT() { 126 | return t; 127 | } 128 | 129 | public void setT(float t) { 130 | this.t = t; 131 | } 132 | 133 | public float getSpeed() { 134 | return speed; 135 | } 136 | 137 | public void setSpeed(float speed) { 138 | this.speed = speed; 139 | } 140 | 141 | public int getType() { 142 | return type; 143 | } 144 | 145 | public void setType(int type) { 146 | this.type = type; 147 | } 148 | 149 | /** 150 | * 重置下x,y坐标 151 | * 位置在最底部的中间 152 | * 153 | * @param x 154 | * @param y 155 | */ 156 | public void initXY(float x, float y) { 157 | this.x = x; 158 | this.y = y; 159 | } 160 | 161 | /** 162 | * 重置起始点和结束点 163 | * 164 | * @param width 165 | * @param height 166 | */ 167 | public void initStartAndEnd(float width, float height) { 168 | //起始点和结束点为view的正下方和正上方 169 | this.startX = width / 2; 170 | this.startY = height; 171 | this.endX = width / 2; 172 | this.endY = 0; 173 | initXY(startX,startY); 174 | } 175 | 176 | /** 177 | * 重置控制点坐标 178 | * 179 | * @param width 180 | * @param height 181 | */ 182 | public void initControl(float width, float height) { 183 | //随机生成控制点1 184 | this.control1X = (float) (Math.random() * width); 185 | this.control1Y = (float) (Math.random() * height); 186 | 187 | //随机生成控制点2 188 | this.control2X = (float) (Math.random() * width); 189 | this.control2Y = (float) (Math.random() * height); 190 | 191 | //如果两个点重合,重新生成控制点 192 | if (this.control1X == this.control2X && this.control1Y == this.control2Y) { 193 | initControl(width, height); 194 | } 195 | } 196 | 197 | /** 198 | * 重置速率 199 | */ 200 | public void initSpeed() { 201 | //随机速率 202 | this.speed = (float) (Math.random() * 0.01 + 0.003); 203 | } 204 | 205 | // /** 206 | // * 重置时间 207 | // * t=0-1; 208 | // */ 209 | // public void resetT() { 210 | // this.t = 0; 211 | // } 212 | } 213 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/heart/HeartActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.heart; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import com.zyyoona7.customviewsets.R; 8 | 9 | import java.util.Random; 10 | 11 | public class HeartActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_heart); 17 | final HeartView heartView= (HeartView) findViewById(R.id.heart_view); 18 | heartView.setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | heartView.addHeart(new Random().nextInt(6)); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/heart/HeartView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.heart; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Matrix; 9 | import android.graphics.Paint; 10 | import android.graphics.PixelFormat; 11 | import android.graphics.PorterDuff; 12 | import android.util.AttributeSet; 13 | import android.util.Log; 14 | import android.util.SparseArray; 15 | import android.view.SurfaceHolder; 16 | import android.view.SurfaceView; 17 | 18 | import com.zyyoona7.customviewsets.R; 19 | 20 | import java.util.concurrent.ConcurrentLinkedQueue; 21 | 22 | /** 23 | * Created by zyyoona7 on 2017/7/3. 24 | * 直播平台进入直播间点亮功能的心形view 25 | */ 26 | 27 | public class HeartView extends SurfaceView implements SurfaceHolder.Callback, Runnable { 28 | 29 | private static final String TAG = "HeartView"; 30 | 31 | private boolean isRunning = false; 32 | private Paint mPaint; 33 | 34 | private ConcurrentLinkedQueue mHearts = null; 35 | 36 | private float mWidth; 37 | private float mHeight; 38 | 39 | private Matrix mMatrix; 40 | 41 | private SparseArray mBitmapSparseArray; 42 | 43 | public HeartView(Context context) { 44 | this(context, null); 45 | } 46 | 47 | public HeartView(Context context, AttributeSet attrs) { 48 | super(context, attrs); 49 | init(context); 50 | } 51 | 52 | private void init(Context context) { 53 | mPaint = new Paint(); 54 | mPaint.setAntiAlias(true); 55 | mPaint.setColor(Color.RED); 56 | mPaint.setStyle(Paint.Style.STROKE); 57 | mPaint.setStrokeWidth(10); 58 | getHolder().addCallback(this); 59 | //设置背景透明 60 | setZOrderOnTop(true); 61 | getHolder().setFormat(PixelFormat.TRANSLUCENT); 62 | //---- 63 | setFocusable(true); 64 | setKeepScreenOn(true); 65 | setFocusableInTouchMode(true); 66 | 67 | mHearts = new ConcurrentLinkedQueue<>(); 68 | mMatrix = new Matrix(); 69 | mBitmapSparseArray = new SparseArray<>(); 70 | Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(), R.drawable.heart_default); 71 | Bitmap bitmap2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ss_heart1); 72 | Bitmap bitmap3 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ss_heart2); 73 | Bitmap bitmap4 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ss_heart3); 74 | Bitmap bitmap5 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ss_heart4); 75 | Bitmap bitmap6 = BitmapFactory.decodeResource(context.getResources(), R.drawable.ss_heart5); 76 | mBitmapSparseArray.put(Heart.DEFAULT, bitmap1); 77 | mBitmapSparseArray.put(Heart.PINK, bitmap2); 78 | mBitmapSparseArray.put(Heart.CYAN, bitmap3); 79 | mBitmapSparseArray.put(Heart.GREEN, bitmap4); 80 | mBitmapSparseArray.put(Heart.YELLOW, bitmap5); 81 | mBitmapSparseArray.put(Heart.BLUE, bitmap6); 82 | } 83 | 84 | @Override 85 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 86 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 87 | } 88 | 89 | @Override 90 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 91 | super.onSizeChanged(w, h, oldw, oldh); 92 | mWidth = getWidth(); 93 | mHeight = getHeight(); 94 | } 95 | 96 | @Override 97 | public void surfaceCreated(SurfaceHolder holder) { 98 | isRunning = true; 99 | new Thread(this).start(); 100 | } 101 | 102 | @Override 103 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 104 | } 105 | 106 | @Override 107 | public void surfaceDestroyed(SurfaceHolder holder) { 108 | isRunning = false; 109 | //回收bitmap 110 | for (int i = 0; i < mBitmapSparseArray.size(); i++) { 111 | if (mBitmapSparseArray.valueAt(i) != null) { 112 | mBitmapSparseArray.valueAt(i).recycle(); 113 | } 114 | } 115 | } 116 | 117 | @Override 118 | public void run() { 119 | while (isRunning) { 120 | Canvas canvas = null; 121 | try { 122 | canvas = getHolder().lockCanvas(); 123 | if (canvas != null) { 124 | //清屏~ 125 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 126 | for (Heart heart : mHearts) { 127 | if (mBitmapSparseArray.get(heart.getType()) == null) { 128 | continue; 129 | } 130 | mMatrix.setTranslate(0, 0); 131 | //位移到x,y 132 | mMatrix.postTranslate(heart.getX(), heart.getY()); 133 | //缩放 134 | // mMatrix.postScale() 135 | //画bitmap 136 | canvas.drawBitmap(mBitmapSparseArray.get(heart.getType()), mMatrix, mPaint); 137 | //计算时间 138 | if (heart.getT() < 1) { 139 | heart.setT(heart.getT() + heart.getSpeed()); 140 | handleBezierXY(heart); 141 | } else { 142 | removeHeart(heart); 143 | } 144 | } 145 | } 146 | } catch (Exception e) { 147 | Log.e(TAG, "run: " + e.getMessage()); 148 | } finally { 149 | if (canvas != null) { 150 | getHolder().unlockCanvasAndPost(canvas); 151 | } 152 | } 153 | 154 | } 155 | } 156 | 157 | /** 158 | * 计算实时的点坐标 159 | * 160 | * @param heart 161 | */ 162 | private void handleBezierXY(Heart heart) { 163 | //三阶贝塞尔曲线函数 164 | //x = (float) (Math.pow((1 - t), 3) * start.x + 3 * t * Math.pow((1 - t), 2) * control1.x + 3 * Math.pow(t, 2) * (1 - t) * control2.x + Math.pow(t, 3) * end.x); 165 | //y = (float) (Math.pow((1 - t), 3) * start.y + 3 * t * Math.pow((1 - t), 2) * control1.y + 3 * Math.pow(t, 2) * (1 - t) * control2.y + Math.pow(t, 3) * end.y); 166 | float x = (float) (Math.pow((1 - heart.getT()), 3) * heart.getStartX() + 3 * heart.getT() * Math.pow((1 - heart.getT()), 2) * heart.getControl1X() + 3 * Math.pow(heart.getT(), 2) * (1 - heart.getT()) * heart.getControl2X() + Math.pow(heart.getT(), 3) * heart.getEndX()); 167 | float y = (float) (Math.pow((1 - heart.getT()), 3) * heart.getStartY() + 3 * heart.getT() * Math.pow((1 - heart.getT()), 2) * heart.getControl1Y() + 3 * Math.pow(heart.getT(), 2) * (1 - heart.getT()) * heart.getControl2Y() + Math.pow(heart.getT(), 3) * heart.getEndY()); 168 | 169 | heart.setX(x); 170 | heart.setY(y); 171 | } 172 | 173 | /** 174 | * 添加heart 175 | */ 176 | public void addHeart() { 177 | Heart heart = new Heart(); 178 | initHeart(heart); 179 | mHearts.add(heart); 180 | } 181 | 182 | /** 183 | * 添加heart 184 | * 185 | * @param type 186 | */ 187 | public void addHeart(int type) { 188 | Heart heart = new Heart(); 189 | initHeart(heart); 190 | heart.setType(type); 191 | mHearts.add(heart); 192 | } 193 | 194 | /** 195 | * 重置 196 | * 197 | * @param heart 198 | */ 199 | private void initHeart(Heart heart) { 200 | heart.initStartAndEnd(mWidth, mHeight); 201 | heart.initControl(mWidth, mHeight); 202 | heart.initSpeed(); 203 | } 204 | 205 | /** 206 | * 移除Heart 207 | * 208 | * @param heart 209 | */ 210 | private void removeHeart(Heart heart) { 211 | mHearts.remove(heart); 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/pagination_rv/CustomAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.pagination_rv; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.ViewGroup; 5 | 6 | import com.chad.library.adapter.base.BaseQuickAdapter; 7 | import com.chad.library.adapter.base.BaseViewHolder; 8 | import com.zyyoona7.customviewsets.R; 9 | 10 | /** 11 | * Created by zyyoona7 on 2016/9/23. 12 | */ 13 | 14 | public class CustomAdapter extends BaseQuickAdapter { 15 | 16 | 17 | public CustomAdapter() { 18 | super(R.layout.item_pagination, null); 19 | } 20 | 21 | @Override 22 | public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 23 | return super.onCreateViewHolder(parent, viewType); 24 | } 25 | 26 | @Override 27 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int positions) { 28 | super.onBindViewHolder(holder, positions); 29 | } 30 | 31 | @Override 32 | protected void convert(BaseViewHolder baseViewHolder, String s) { 33 | baseViewHolder.setText(R.id.tv_item, s); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/pagination_rv/CustomLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.pagination_rv; 2 | 3 | import android.graphics.Rect; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.Log; 6 | import android.util.SparseArray; 7 | import android.view.View; 8 | 9 | /** 10 | * Created by zyyoona7 on 2016/9/22. 11 | */ 12 | 13 | public class CustomLayoutManager extends RecyclerView.LayoutManager { 14 | private static final String TAG = "CustomLayoutManager"; 15 | 16 | //保存所有的Item的上下左右的偏移量信息 17 | private SparseArray mAllItemsFrames; 18 | 19 | private int mHorizontalScrollOffset = 0; 20 | 21 | private int mTotalWidth = 0; 22 | 23 | //每页的行列数 24 | private int mSpanColumns = 1; 25 | 26 | private int mSpanRows = 1; 27 | 28 | @Override 29 | public RecyclerView.LayoutParams generateDefaultLayoutParams() { 30 | return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); 31 | } 32 | 33 | @Override 34 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 35 | //如果没有item,直接返回 36 | if (getItemCount() <= 0) return; 37 | // 跳过preLayout,preLayout主要用于支持动画 38 | if (state.isPreLayout()) { 39 | return; 40 | } 41 | //在布局之前,将所有的子View先Detach掉,放入到Scrap缓存中 42 | detachAndScrapAttachedViews(recycler); 43 | mAllItemsFrames = new SparseArray<>(getItemCount()); 44 | int currentWidth = 0; 45 | int currentHeight = 0; 46 | int currentPage = 1; 47 | int maxCurrentWidth = 0; 48 | View scrap = recycler.getViewForPosition(0); 49 | addView(scrap); 50 | measureChildWithMargins(scrap, 0, 0); 51 | int width = getDecoratedMeasuredWidth(scrap); 52 | int height = getDecoratedMeasuredHeight(scrap); 53 | detachAndScrapView(scrap, recycler); 54 | maxCurrentWidth = (getHorizontalSpace() / width) * width; 55 | Log.e(TAG, "onLayoutChildren: itemCount=" + getItemCount()); 56 | for (int i = 0; i < getItemCount(); i++) { 57 | //这里就是从缓存里面取出 58 | // View view = recycler.getViewForPosition(i); 59 | //将View加入到RecyclerView中 60 | // addView(view); 61 | // measureChildWithMargins(view, 0, 0); 62 | 63 | Rect frame = mAllItemsFrames.get(i); 64 | if (frame == null) { 65 | frame = new Rect(); 66 | } 67 | if (currentWidth + width > maxCurrentWidth * currentPage) { 68 | currentHeight += height; 69 | currentWidth = maxCurrentWidth * (currentPage - 1); 70 | } 71 | 72 | if (currentHeight + height > getVerticalSpace()) { 73 | currentWidth = maxCurrentWidth * currentPage; 74 | currentHeight = 0; 75 | currentPage++; 76 | } 77 | Log.e(TAG, "onLayoutChildren: currentWidth=" + currentWidth + ",currentHeight=" + currentHeight + ",horizontalSpan=" + getHorizontalSpace() + ",verticalSpace=" + getVerticalSpace() + ",currentPage=" + currentPage + ",maxWidth=" + maxCurrentWidth 78 | ); 79 | frame.set(currentWidth, currentHeight, currentWidth + width, currentHeight + height); 80 | // 将当前的Item的Rect边界数据保存 81 | mAllItemsFrames.put(i, frame); 82 | 83 | currentWidth += width; 84 | mTotalWidth = maxCurrentWidth * currentPage; 85 | Log.e(TAG, "onLayoutChildren: totalWidth=" + mTotalWidth); 86 | } 87 | 88 | mTotalWidth = Math.max(mTotalWidth, getHorizontalSpace()); 89 | recycleAndFillItems(recycler, state); 90 | } 91 | 92 | /** 93 | * 回收不需要的Item,并且将需要显示的Item从缓存中取出 94 | */ 95 | private void recycleAndFillItems(RecyclerView.Recycler recycler, RecyclerView.State state) { 96 | if (state.isPreLayout()) { // 跳过preLayout,preLayout主要用于支持动画 97 | return; 98 | } 99 | 100 | // 当前scroll offset状态下的显示区域 101 | Rect displayFrame = new Rect(mHorizontalScrollOffset, 0, mHorizontalScrollOffset + getHorizontalSpace(), getVerticalSpace()); 102 | 103 | /** 104 | * 将滑出屏幕的Items回收到Recycle缓存中 105 | */ 106 | Rect childFrame = new Rect(); 107 | for (int i = 0; i < getChildCount(); i++) { 108 | View child = getChildAt(i); 109 | childFrame.left = getDecoratedLeft(child); 110 | childFrame.top = getDecoratedTop(child); 111 | childFrame.right = getDecoratedRight(child); 112 | childFrame.bottom = getDecoratedBottom(child); 113 | //如果Item没有在显示区域,就说明需要回收 114 | if (!Rect.intersects(displayFrame, childFrame)) { 115 | //回收掉滑出屏幕的View 116 | removeAndRecycleView(child,recycler); 117 | 118 | } 119 | } 120 | 121 | //重新显示需要出现在屏幕的子View 122 | for (int i = 0; i < getItemCount(); i++) { 123 | if (Rect.intersects(displayFrame, mAllItemsFrames.get(i))) { 124 | 125 | View scrap = recycler.getViewForPosition(i); 126 | measureChildWithMargins(scrap, 0, 0); 127 | addView(scrap); 128 | 129 | Rect frame = mAllItemsFrames.get(i); 130 | //将这个item布局出来 131 | layoutDecorated(scrap, 132 | frame.left - mHorizontalScrollOffset, 133 | frame.top, 134 | frame.right - mHorizontalScrollOffset, 135 | frame.bottom); 136 | 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { 143 | //先detach掉所有的子View 144 | detachAndScrapAttachedViews(recycler); 145 | 146 | //实际要滑动的距离 147 | int travel = dx; 148 | // 149 | // 如果滑动到最左面 150 | if (mHorizontalScrollOffset + dx < 0) { 151 | travel = -mHorizontalScrollOffset; 152 | } else if (mHorizontalScrollOffset + dx > mTotalWidth - getHorizontalSpace()) {//如果滑动到最右部 153 | travel = mTotalWidth - getHorizontalSpace() - mHorizontalScrollOffset; 154 | } 155 | // 156 | // 将水平方向的偏移量+travel 157 | mHorizontalScrollOffset += travel; 158 | offsetChildrenHorizontal(-travel); 159 | recycleAndFillItems(recycler, state); 160 | Log.e(TAG, "scrollHorizontallyBy: childCount=" + getChildCount()); 161 | return travel; 162 | } 163 | 164 | @Override 165 | public boolean canScrollHorizontally() { 166 | return true; 167 | } 168 | 169 | 170 | private int getVerticalSpace() { 171 | return getHeight() - getPaddingBottom() - getPaddingTop(); 172 | } 173 | 174 | private int getHorizontalSpace() { 175 | return getWidth() - getPaddingRight() - getPaddingLeft(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/pagination_rv/LayoutManagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.pagination_rv; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.v4.view.ViewCompat; 6 | import android.support.v7.widget.GridLayoutManager; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | 11 | import com.chad.library.adapter.base.BaseQuickAdapter; 12 | import com.zyyoona7.customviewsets.BaseActivity; 13 | import com.zyyoona7.customviewsets.R; 14 | 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | 18 | import butterknife.BindView; 19 | 20 | public class LayoutManagerActivity extends BaseActivity { 21 | 22 | private static final String TAG = "LayoutManagerActivity"; 23 | 24 | @BindView(R.id.id_layout_manager_recycler_view) 25 | RecyclerView mRecyclerView; 26 | 27 | int currentVisiblePosition = 0; 28 | int currentLastVisiblePosition = 0; 29 | LinkedList list = new LinkedList<>(); 30 | 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | mRecyclerView.setLayoutManager(new GridLayoutManager(this, 4, LinearLayoutManager.HORIZONTAL, false)); 36 | // mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)); 37 | final CustomAdapter adapter = new CustomAdapter(); 38 | mRecyclerView.setAdapter(adapter); 39 | adapter.setNewData(createData()); 40 | // SnapHelper snapHelper = new PaginationSnapHelper(4); 41 | // snapHelper.attachToRecyclerView(mRecyclerView); 42 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 43 | @Override 44 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 45 | super.onScrollStateChanged(recyclerView, newState); 46 | // Log.e(TAG, "onScrollStateChanged: state=" + newState); 47 | if (RecyclerView.SCROLL_STATE_IDLE == newState) { 48 | // int visiblePosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); 49 | // int visibleCount = ((GridLayoutManager) recyclerView.getLayoutManager()).getChildCount(); 50 | // int itemCount = recyclerView.getLayoutManager().getItemCount(); 51 | // if (visibleCount + visiblePosition >= itemCount) { 52 | // Log.e(TAG, "onScrolled: end"); 53 | // ((BaseQuickAdapter) recyclerView.getAdapter()).addData(createData()); 54 | // } 55 | Log.e(TAG, "onScrollStateChanged: stopped" ); 56 | } 57 | } 58 | 59 | @Override 60 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 61 | super.onScrolled(recyclerView, dx, dy); 62 | // Log.e(TAG, "onScrolled: dx=" + dx + ",dy=" + dy); 63 | int visiblePosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); 64 | int lastVisiblePosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition(); 65 | // Log.e(TAG, "onScrolled:first visible position=" + visiblePosition); 66 | // Log.e(TAG, "onScrolled:last visible position=" + lastVisiblePosition); 67 | // Log.e(TAG, "onScrolled:current visible position=" + currentVisiblePosition); 68 | if (visiblePosition != currentVisiblePosition && visiblePosition > currentVisiblePosition) { 69 | //next page 70 | currentVisiblePosition = visiblePosition; 71 | // currentLastVisiblePosition = visiblePosition + 3; 72 | // onNextPage(currentVisiblePosition); 73 | Log.e(TAG, "onScrolled: next page"); 74 | } else if (visiblePosition!= currentVisiblePosition && visiblePosition < currentVisiblePosition) { 75 | //pre page 76 | currentVisiblePosition = visiblePosition; 77 | // currentVisiblePosition = lastVisiblePosition - 3; 78 | // onPrePage(currentVisiblePosition); 79 | Log.e(TAG, "onScrolled: pre page"); 80 | } 81 | //最左边 82 | // Log.e(TAG, "onScrolled: can Scroll Horizontal=" + recyclerView.canScrollHorizontally(-1)); 83 | if (!recyclerView.canScrollHorizontally(-1)) { 84 | //left refresh 85 | } 86 | //最右边 87 | // Log.e(TAG, "onScrolled: can Scroll Horizontal=" + recyclerView.canScrollHorizontally(1)); 88 | if (!recyclerView.canScrollHorizontally(1)) { 89 | //right load more 90 | // int visiblePosition = ((GridLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); 91 | // int visibleCount = ((GridLayoutManager) recyclerView.getLayoutManager()).getChildCount(); 92 | // int itemCount = recyclerView.getLayoutManager().getItemCount(); 93 | // if (visibleCount + visiblePosition >= itemCount) { 94 | Log.e(TAG, "onScrolled: end"); 95 | adapter.addData(createData()); 96 | // } 97 | } 98 | } 99 | }); 100 | } 101 | 102 | @Override 103 | protected int getContentViewID() { 104 | return R.layout.activity_layout_manager; 105 | } 106 | 107 | private List createData() { 108 | for (int i = 0; i < 50; i++) { 109 | list.add("position=" + i); 110 | } 111 | return list; 112 | } 113 | 114 | private void onNextPage(int currentPosition) { 115 | for (int i = list.size(); i < list.size() + 4; i++) { 116 | list.add("position=" + i); 117 | } 118 | 119 | for (int i = 0; i < 4; i++) { 120 | list.removeFirst(); 121 | } 122 | } 123 | 124 | private void onPrePage(int currentPosition) { 125 | for (int i = 0; i < 4; i++) { 126 | list.addFirst("position=" + i); 127 | } 128 | 129 | for (int i = list.size() - 1; i > list.size() - 5; i--) { 130 | list.removeLast(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/pagination_rv/PaginationSnapHelper.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.pagination_rv; 2 | 3 | import android.support.v7.widget.LinearSnapHelper; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | /** 8 | * Created by zyyoona7 on 2016/9/29. 9 | *

10 | * code from http://stackoverflow.com/questions/29134094/recyclerview-horizontal-scroll-snap-in-center 11 | */ 12 | 13 | public class PaginationSnapHelper extends LinearSnapHelper { 14 | 15 | private static final String TAG = "PaginationSnapHelper"; 16 | 17 | 18 | private int mPageSize = 1; 19 | 20 | public PaginationSnapHelper(int pageSize) { 21 | this.mPageSize = pageSize; 22 | } 23 | 24 | 25 | @Override 26 | public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { 27 | View centerView = findSnapView(layoutManager); 28 | if (centerView == null) { 29 | return RecyclerView.NO_POSITION; 30 | } 31 | 32 | int position = layoutManager.getPosition(centerView); 33 | int targetPosition = -1; 34 | if (layoutManager.canScrollHorizontally()) { 35 | if (velocityX < 0) { 36 | targetPosition = position - mPageSize; 37 | } else { 38 | targetPosition = position + mPageSize; 39 | } 40 | } 41 | 42 | if (layoutManager.canScrollVertically()) { 43 | if (velocityY < 0) { 44 | targetPosition = position - mPageSize; 45 | } else { 46 | targetPosition = position + mPageSize; 47 | } 48 | } 49 | 50 | final int firstItem = 0; 51 | final int lastItem = layoutManager.getItemCount() - 1; 52 | targetPosition = Math.min(lastItem, Math.max(targetPosition, firstItem)); 53 | return targetPosition; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/pagination_rv/SnappyRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.pagination_rv; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.View; 10 | 11 | /** 12 | * Created by zyyoona7 on 2016/9/29. 13 | *

14 | * code from http://stackoverflow.com/questions/29134094/recyclerview-horizontal-scroll-snap-in-center 15 | */ 16 | public class SnappyRecyclerView extends RecyclerView { 17 | private static final String TAG = "SnappyRecyclerView"; 18 | 19 | // Use it with a horizontal LinearLayoutManager 20 | // Based on http://stackoverflow.com/a/29171652/4034572 21 | 22 | public SnappyRecyclerView(Context context) { 23 | super(context); 24 | } 25 | 26 | public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs) { 27 | super(context, attrs); 28 | } 29 | 30 | public SnappyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 31 | super(context, attrs, defStyle); 32 | } 33 | 34 | @Override 35 | public boolean fling(int velocityX, int velocityY) { 36 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); 37 | 38 | // int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; 39 | int screenWidth = getMeasuredWidth(); 40 | // views on the screen 41 | int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition(); 42 | View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition); 43 | int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition(); 44 | View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition); 45 | 46 | // distance we need to scroll 47 | int leftMargin = (screenWidth - lastView.getWidth()) / 2; 48 | int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth(); 49 | int leftEdge = lastView.getLeft(); 50 | int rightEdge = firstView.getRight(); 51 | int scrollDistanceLeft = leftEdge - leftMargin; 52 | int scrollDistanceRight = rightMargin - rightEdge; 53 | 54 | if (Math.abs(velocityX) < 1000) { 55 | // The fling is slow -> stay at the current page if we are less than half through, 56 | // or go to the next page if more than half through 57 | 58 | if (leftEdge > screenWidth / 2) { 59 | // go to next page 60 | smoothScrollBy(-scrollDistanceRight, 0); 61 | } else if (rightEdge < screenWidth / 2) { 62 | // go to next page 63 | smoothScrollBy(scrollDistanceLeft, 0); 64 | } else { 65 | // stay at current page 66 | if (velocityX > 0) { 67 | smoothScrollBy(-scrollDistanceRight, 0); 68 | } else { 69 | smoothScrollBy(scrollDistanceLeft, 0); 70 | } 71 | } 72 | return true; 73 | 74 | } else { 75 | // The fling is fast -> go to next page 76 | 77 | if (velocityX > 0) { 78 | smoothScrollBy(scrollDistanceLeft, 0); 79 | } else { 80 | smoothScrollBy(-scrollDistanceRight, 0); 81 | } 82 | return true; 83 | 84 | } 85 | 86 | } 87 | 88 | @Override 89 | public void onScrollStateChanged(int state) { 90 | super.onScrollStateChanged(state); 91 | 92 | // If you tap on the phone while the RecyclerView is scrolling it will stop in the middle. 93 | // This code fixes this. This code is not strictly necessary but it improves the behaviour. 94 | 95 | if (state == SCROLL_STATE_IDLE) { 96 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager(); 97 | 98 | // int screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; 99 | int screenWidth = getMeasuredWidth(); 100 | // views on the screen 101 | int lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition(); 102 | View lastView = linearLayoutManager.findViewByPosition(lastVisibleItemPosition); 103 | int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition(); 104 | View firstView = linearLayoutManager.findViewByPosition(firstVisibleItemPosition); 105 | 106 | // distance we need to scroll 107 | int leftMargin = (screenWidth - lastView.getWidth()) / 2; 108 | int rightMargin = (screenWidth - firstView.getWidth()) / 2 + firstView.getWidth(); 109 | int leftEdge = lastView.getLeft(); 110 | int rightEdge = firstView.getRight(); 111 | int scrollDistanceLeft = leftEdge - leftMargin; 112 | int scrollDistanceRight = rightMargin - rightEdge; 113 | 114 | if (leftEdge > screenWidth / 2) { 115 | smoothScrollBy(-scrollDistanceRight, 0); 116 | } else if (rightEdge < screenWidth / 2) { 117 | smoothScrollBy(scrollDistanceLeft, 0); 118 | } 119 | } 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/utils/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.utils; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * A utils of screen density. 7 | *

8 | * it can help you convert the different unit. 9 | *

10 | * E.g: dp -> px ps->dp sp->px px->sp 11 | *

12 | * Author: GcsSloop 13 | *

14 | * Created Date: 16/5/24 15 | *

16 | * Copyright (C) 2016 GcsSloop. 17 | *

18 | * GitHub: https://github.com/GcsSloop 19 | */ 20 | public class DensityUtils { 21 | 22 | public DensityUtils() { 23 | } 24 | 25 | /** 26 | * convert the dp to px depend on the device density. 27 | * 28 | * @param context the context 29 | * @param dpValue a value of dp 30 | * @return the result of px 31 | */ 32 | public static int dip2px(Context context, float dpValue) { 33 | return (int) (dpValue * getDensity(context) + 0.5f); 34 | } 35 | 36 | /** 37 | * convert the px to dp depend on the device density. 38 | * 39 | * @param context the context 40 | * @param pxValue a value of px 41 | * @return the result of dp 42 | */ 43 | public static int px2dip(Context context, float pxValue) { 44 | return (int) (pxValue / getDensity(context) + 0.5f); 45 | } 46 | 47 | /** 48 | * convert the sp to px depend on the device scaledDensity. 49 | * 50 | * @param context the context 51 | * @param spValue a value of sp 52 | * @return the result of px 53 | */ 54 | public static int sp2px(Context context, float spValue) { 55 | return (int) (spValue * getFontDensity(context) + 0.5); 56 | } 57 | 58 | /** 59 | * convert the px to sp depend on the device scaledDensity. 60 | * 61 | * @param context the context 62 | * @param pxValue a value of px 63 | * @return the result of sp 64 | */ 65 | public static int px2sp(Context context, float pxValue) { 66 | return (int) (pxValue / getFontDensity(context) + 0.5); 67 | } 68 | 69 | /** 70 | * get the density of device screen. 71 | * 72 | * @param context the context 73 | * @return the screen density 74 | */ 75 | public static float getDensity(Context context) { 76 | return context.getResources().getDisplayMetrics().density; 77 | } 78 | 79 | /** 80 | * get the scale density of device screen. 81 | * usually this value is the same as density. 82 | * but it can adjust by user. 83 | * 84 | * @param context the context 85 | * @return the screen scale density. 86 | */ 87 | public static float getFontDensity(Context context) { 88 | return context.getResources().getDisplayMetrics().scaledDensity; 89 | } 90 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/OnItemSelectedListener.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * item选中回调 7 | */ 8 | public interface OnItemSelectedListener { 9 | 10 | /** 11 | * item选中回调方法 12 | * 13 | * @param view 14 | * @param position 15 | */ 16 | void onItemSelected(View view, int position); 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/OnZoomAnimatorListener.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * 缩放动画回调 7 | */ 8 | public interface OnZoomAnimatorListener { 9 | /** 10 | * 放大动画开始 11 | * 12 | * @param view 13 | */ 14 | void onZoomInStart(View view); 15 | 16 | /** 17 | * 放大动画结束 18 | * 19 | * @param view 20 | */ 21 | void onZoomInEnd(View view); 22 | 23 | /** 24 | * 缩小动画开始 25 | * 26 | * @param view 27 | */ 28 | void onZoomOutStart(View view); 29 | 30 | /** 31 | * 缩小动画结束 32 | * 33 | * @param view 34 | */ 35 | void onZoomOutEnd(View view); 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/TestZoomHoverAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import com.zyyoona7.customviewsets.R; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by User on 2016/8/16. 14 | */ 15 | 16 | public class TestZoomHoverAdapter extends ZoomHoverAdapter { 17 | 18 | 19 | public TestZoomHoverAdapter(List list) { 20 | super(list); 21 | } 22 | 23 | @Override 24 | public View getView(ViewGroup parent, int position, String s) { 25 | View contentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_overlay_card, parent, false); 26 | TextView textView= (TextView) contentView.findViewById(R.id.tv_item); 27 | textView.setText(s); 28 | return contentView; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverActivity.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.util.SimpleArrayMap; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import com.zyyoona7.customviewsets.BaseActivity; 10 | import com.zyyoona7.customviewsets.R; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import butterknife.BindView; 16 | 17 | public class ZoomHoverActivity extends BaseActivity { 18 | private static final String TAG = "ZoomHoverActivity"; 19 | List mList = new ArrayList<>(); 20 | TestZoomHoverAdapter mAdapter; 21 | @BindView(R.id.zoom_hover_grid_view) 22 | ZoomHoverGridView mZoomHoverGridView; 23 | @BindView(R.id.zoom_hover_view) 24 | ZoomHoverView mZoomHoverView; 25 | 26 | @Override 27 | protected int getContentViewID() { 28 | return R.layout.activity_zoom_hover; 29 | } 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | mList.add("test 1"); 35 | mList.add("test 2"); 36 | mList.add("test 3"); 37 | mList.add("test 4"); 38 | mList.add("test 5"); 39 | mList.add("test 6"); 40 | mList.add("test 7"); 41 | mList.add("test 8"); 42 | mList.add("test 9"); 43 | mList.add("test 10"); 44 | mAdapter = new TestZoomHoverAdapter(mList); 45 | final SimpleArrayMap map = new SimpleArrayMap<>(); 46 | map.put(0, 2); 47 | mZoomHoverView.setSpan(map); 48 | mZoomHoverView.setAdapter(mAdapter); 49 | 50 | mZoomHoverView.setOnZoomAnimatorListener(new OnZoomAnimatorListener() { 51 | @Override 52 | public void onZoomInStart(View view) { 53 | view.setBackground(getResources().getDrawable(android.R.drawable.dialog_holo_light_frame)); 54 | } 55 | 56 | @Override 57 | public void onZoomInEnd(View view) { 58 | 59 | } 60 | 61 | @Override 62 | public void onZoomOutStart(View view) { 63 | 64 | } 65 | 66 | @Override 67 | public void onZoomOutEnd(View view) { 68 | view.setBackgroundColor(getResources().getColor(R.color.colorAccent)); 69 | } 70 | }); 71 | mZoomHoverGridView.setOnZoomAnimatorListener(new OnZoomAnimatorListener() { 72 | @Override 73 | public void onZoomInStart(View view) { 74 | view.setBackground(getResources().getDrawable(android.R.drawable.dialog_holo_light_frame)); 75 | } 76 | 77 | @Override 78 | public void onZoomInEnd(View view) { 79 | 80 | } 81 | 82 | @Override 83 | public void onZoomOutStart(View view) { 84 | 85 | } 86 | 87 | @Override 88 | public void onZoomOutEnd(View view) { 89 | view.setBackgroundColor(getResources().getColor(R.color.colorAccent)); 90 | } 91 | }); 92 | 93 | mZoomHoverView.setOnItemSelectedListener(new OnItemSelectedListener() { 94 | @Override 95 | public void onItemSelected(View view, int position) { 96 | Toast.makeText(ZoomHoverActivity.this, "selected position=" + position, Toast.LENGTH_SHORT).show(); 97 | } 98 | }); 99 | 100 | mZoomHoverView.postDelayed(new Runnable() { 101 | @Override 102 | public void run() { 103 | mZoomHoverView.setSelectedItem(1); 104 | } 105 | }, 2000); 106 | 107 | mZoomHoverView.postDelayed(new Runnable() { 108 | @Override 109 | public void run() { 110 | mZoomHoverView.setSelectedItem(3); 111 | } 112 | }, 3000); 113 | 114 | mZoomHoverView.postDelayed(new Runnable() { 115 | @Override 116 | public void run() { 117 | mZoomHoverView.setSelectedItem(0); 118 | } 119 | }, 4000); 120 | // 121 | mZoomHoverGridView.addSpanItem(0, 1, 2); 122 | mZoomHoverGridView.addSpanItem(3, 2, 2); 123 | mZoomHoverGridView.addSpanItem(4, 2, 1); 124 | mZoomHoverGridView.addSpanItem(5, 1, 3); 125 | 126 | mZoomHoverGridView.setAdapter(mAdapter); 127 | } 128 | 129 | @Override 130 | protected void onResume() { 131 | super.onResume(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by zyyoona7 on 2016/8/16. 12 | */ 13 | 14 | public abstract class ZoomHoverAdapter { 15 | 16 | private List mDataList; 17 | 18 | //数据变化回调 19 | private OnDataChangedListener mOnDataChangedListener; 20 | 21 | 22 | /** 23 | * 数据变化回调 24 | */ 25 | interface OnDataChangedListener { 26 | void onChanged(); 27 | } 28 | 29 | 30 | public ZoomHoverAdapter(List list) { 31 | this.mDataList = list; 32 | } 33 | 34 | public ZoomHoverAdapter(T[] datas) { 35 | this.mDataList = new ArrayList<>(Arrays.asList(datas)); 36 | } 37 | 38 | /** 39 | * 设置数据变化监听器 40 | * 41 | * @param listener 42 | */ 43 | public void setDataChangedListener(OnDataChangedListener listener) { 44 | this.mOnDataChangedListener = listener; 45 | } 46 | 47 | /** 48 | * 通知adapter数据发生变化 49 | */ 50 | public void notifyDataChanged() { 51 | if (this.mOnDataChangedListener != null) { 52 | this.mOnDataChangedListener.onChanged(); 53 | } 54 | } 55 | 56 | /** 57 | * 获取数据的总数 58 | * 59 | * @return 60 | */ 61 | public int getCount() { 62 | return mDataList == null ? 0 : mDataList.size(); 63 | } 64 | 65 | /** 66 | * 获取对应Item的bean 67 | * 68 | * @param position 69 | * @return 70 | */ 71 | public T getItem(int position) { 72 | if (mDataList == null) { 73 | return null; 74 | } else { 75 | return mDataList.get(position); 76 | } 77 | } 78 | 79 | public abstract View getView(ViewGroup parent, int position, T t); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverGridView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.os.Build; 10 | import android.support.v4.util.SimpleArrayMap; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.animation.AccelerateDecelerateInterpolator; 14 | import android.view.animation.Interpolator; 15 | import android.widget.GridLayout; 16 | 17 | import com.zyyoona7.customviewsets.R; 18 | import com.zyyoona7.customviewsets.utils.DensityUtils; 19 | 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import static android.os.Build.VERSION_CODES.KITKAT; 26 | 27 | /** 28 | * Created by zyyoona7 on 2016/8/29. 29 | */ 30 | 31 | public class ZoomHoverGridView extends GridLayout implements View.OnClickListener, ZoomHoverAdapter.OnDataChangedListener { 32 | 33 | private static final String TAG = "ZoomHoverGridView"; 34 | 35 | //adapter 36 | private ZoomHoverAdapter mZoomHoverAdapter; 37 | 38 | // 需要的列数 39 | private int mColumnNum = 3; 40 | 41 | // 需要的行数 42 | private int mRowNum = 3; 43 | 44 | //子view距离父控件的外边距宽度 45 | private int mMarginParent = 20; 46 | 47 | //行列的分割线宽度 48 | private int mDivider = 10; 49 | 50 | //当前放大动画 51 | private AnimatorSet mCurrentZoomInAnim = null; 52 | 53 | //当前缩小动画 54 | private AnimatorSet mCurrentZoomOutAnim = null; 55 | 56 | //缩放动画监听器 57 | private OnZoomAnimatorListener mOnZoomAnimatorListener = null; 58 | 59 | //动画持续时间 60 | private int mAnimDuration; 61 | 62 | //动画缩放倍数 63 | private float mAnimZoomTo; 64 | 65 | //缩放动画插值器 66 | private Interpolator mZoomInInterpolator; 67 | private Interpolator mZoomOutInterpolator; 68 | 69 | //上一个ZoomOut的view(为了解决快速切换时,上一个被缩小的view缩放大小不正常的情况) 70 | private View mPreZoomOutView; 71 | 72 | //当前被选中的view 73 | private View mCurrentView = null; 74 | 75 | //item选中监听器 76 | private OnItemSelectedListener mOnItemSelectedListener; 77 | 78 | //存储当前layout中所有子view 79 | private List mViewList = new ArrayList<>(); 80 | 81 | //存储Span信息的list 82 | private SimpleArrayMap mSpanMap = new SimpleArrayMap<>(); 83 | 84 | //存储每个item 85 | private List mItemList = new ArrayList<>(); 86 | 87 | //矩阵,用来存储位置信息 88 | private boolean mMatrix[][]; 89 | 90 | //基准的宽高 91 | private int mBaseWidth = 0; 92 | private int mBaseHeight = 0; 93 | //是否使用基准宽高 94 | private boolean mUseBaseWH = false; 95 | 96 | //确定位置时起始地行列值,防止每次从0开始循环 97 | private int mStartColumn = 0; 98 | private int mStartRow = 0; 99 | 100 | public ZoomHoverGridView(Context context) { 101 | this(context, null); 102 | } 103 | 104 | public ZoomHoverGridView(Context context, AttributeSet attrs) { 105 | super(context, attrs); 106 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZoomHoverView); 107 | mDivider = typedArray.getDimensionPixelOffset(R.styleable.ZoomHoverView_zhv_divider, DensityUtils.dip2px(context, 5)); 108 | mMarginParent = typedArray.getDimensionPixelOffset(R.styleable.ZoomHoverView_zhv_margin_parent, DensityUtils.dip2px(context, 5)); 109 | mColumnNum = typedArray.getInt(R.styleable.ZoomHoverView_zhv_column_num, 3); 110 | mAnimDuration = typedArray.getInt(R.styleable.ZoomHoverView_zhv_zoom_duration, getResources().getInteger(android.R.integer.config_shortAnimTime)); 111 | mAnimZoomTo = typedArray.getFloat(R.styleable.ZoomHoverView_zhv_zoom_to, 1.2f); 112 | mUseBaseWH = typedArray.getBoolean(R.styleable.ZoomHoverView_zhv_use_baseWH, false); 113 | mBaseWidth = typedArray.getDimensionPixelSize(R.styleable.ZoomHoverView_zhv_base_width, DensityUtils.dip2px(context, 50)); 114 | mBaseHeight = typedArray.getDimensionPixelSize(R.styleable.ZoomHoverView_zhv_base_height, DensityUtils.dip2px(context, 50)); 115 | typedArray.recycle(); 116 | mZoomOutInterpolator = mZoomInInterpolator = new AccelerateDecelerateInterpolator(); 117 | } 118 | 119 | @Override 120 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 121 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 122 | 123 | } 124 | 125 | @Override 126 | protected void onDraw(Canvas canvas) { 127 | super.onDraw(canvas); 128 | } 129 | 130 | /** 131 | * 设置适配器 132 | * 133 | * @param adapter 134 | */ 135 | 136 | public void setAdapter(ZoomHoverAdapter adapter) { 137 | this.mZoomHoverAdapter = adapter; 138 | mZoomHoverAdapter.setDataChangedListener(this); 139 | changeAdapter(); 140 | } 141 | 142 | 143 | /** 144 | * 根据adapter添加view 145 | */ 146 | private void changeAdapter() { 147 | //移除所有view 148 | removeAllViews(); 149 | //计算子view并保存到list中 150 | calculateChild(); 151 | //计算每个view的下标并设置位置 152 | calculatePosition(); 153 | //计算子view的实际位置 154 | calculatePositionExact(); 155 | //添加view 156 | addItem(); 157 | } 158 | 159 | /** 160 | * 计算子view的个数并添加到list中 161 | */ 162 | private void calculateChild() { 163 | mViewList.clear(); 164 | mItemList.clear(); 165 | 166 | for (int i = 0; i < mZoomHoverAdapter.getCount(); i++) { 167 | View childView = mZoomHoverAdapter.getView(this, i, mZoomHoverAdapter.getItem(i)); 168 | //添加view到mViewList 169 | mViewList.add(childView); 170 | childView.setId(i + 1); 171 | if (mSpanMap.containsKey(i)) { 172 | int tempRowSpan = mSpanMap.get(i).getRowSpan(); 173 | int tempColumnSpan = mSpanMap.get(i).getColumnSpan(); 174 | tempRowSpan = tempRowSpan >= 1 ? tempRowSpan : 1; 175 | tempColumnSpan = tempColumnSpan >= 1 ? tempColumnSpan : 1; 176 | if (tempColumnSpan > mColumnNum) { 177 | tempColumnSpan = mColumnNum; 178 | } 179 | //添加item到mItemList 180 | mItemList.add(new ZoomHoverItem(tempRowSpan, tempColumnSpan)); 181 | } else { 182 | //添加item到mItemList 183 | mItemList.add(new ZoomHoverItem(1, 1)); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * 计算每个item的下标 190 | */ 191 | private void calculatePosition() { 192 | //计算最大的行数和列数 193 | calculateMaxRowColumn(); 194 | setColumnCount(mColumnNum); 195 | setRowCount(mRowNum); 196 | //初始化数组 197 | mMatrix = new boolean[mRowNum][mColumnNum]; 198 | //循环确定每个item的位置 199 | for (ZoomHoverItem item : mItemList) { 200 | setPosition(item); 201 | } 202 | } 203 | 204 | /** 205 | * 测量最差的情况下行数 206 | */ 207 | private void calculateMaxRowColumn() { 208 | for (ZoomHoverItem item : mItemList) { 209 | mRowNum += item.getRowSpan(); 210 | } 211 | } 212 | 213 | /** 214 | * 设置每个item的位置 215 | * 216 | * @param item 217 | */ 218 | private void setPosition(ZoomHoverItem item) { 219 | //遍历每行 220 | for (int i = mStartRow; i < mRowNum; i++) { 221 | //换行以后重置起始列 222 | mStartColumn = 0; 223 | //遍历每列 224 | for (int j = mStartColumn; j < mColumnNum; j++) { 225 | if (mRowNum < i + item.getRowSpan() || mColumnNum < j + item.getColumnSpan()) { 226 | continue; 227 | } 228 | //判断是否可以放在该区域 229 | if (!canPutPosition(item, i, j)) { 230 | continue; 231 | } 232 | item.setRow(i); 233 | item.setColumn(j); 234 | mStartColumn = j; 235 | mStartRow = i; 236 | //标记已使用的位置 237 | markPosition(item, i, j); 238 | return; 239 | } 240 | } 241 | } 242 | 243 | /** 244 | * 确定是否可以放在此位置 245 | * 246 | * @param item 247 | * @param row 248 | * @param column 249 | * @return 250 | */ 251 | private boolean canPutPosition(ZoomHoverItem item, int row, int column) { 252 | //从当前位置到span的区域判断 253 | for (int i = row; i < row + item.getRowSpan(); i++) { 254 | for (int j = column; j < column + item.getColumnSpan(); j++) { 255 | if (mMatrix[i][j]) { 256 | return false; 257 | } 258 | } 259 | } 260 | return true; 261 | } 262 | 263 | /** 264 | * 标记使用过的位置 265 | * 266 | * @param item 267 | * @param row 268 | * @param column 269 | */ 270 | private void markPosition(ZoomHoverItem item, int row, int column) { 271 | //从当前位置到span的区域判断 272 | for (int i = row; i < row + item.getRowSpan(); i++) { 273 | for (int j = column; j < column + item.getColumnSpan(); j++) { 274 | mMatrix[i][j] = true; 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * 计算布局精确的行数和列数 281 | */ 282 | private void calculatePositionExact() { 283 | mColumnNum = 1; 284 | mRowNum = 1; 285 | //由行数为key,value为每行的列数 286 | HashMap rowMap = new HashMap<>(); 287 | //由列数为key,value为每列的行数 288 | HashMap columnMap = new HashMap<>(); 289 | 290 | for (ZoomHoverItem item : mItemList) { 291 | int row = item.getRow(); 292 | int column = item.getColumn(); 293 | 294 | if (rowMap.containsKey(row)) { 295 | //如果为同一行则列数相加 296 | rowMap.put(row, rowMap.get(row) + item.getColumnSpan()); 297 | } else { 298 | rowMap.put(row, item.getColumnSpan()); 299 | } 300 | 301 | if (columnMap.containsKey(column)) { 302 | //如果同一列,则行数相加 303 | columnMap.put(column, columnMap.get(column) + item.getRowSpan()); 304 | } else { 305 | columnMap.put(column, item.getRowSpan()); 306 | } 307 | } 308 | 309 | Set rowKeySet = rowMap.keySet(); 310 | //遍历取出最大列数 311 | for (Integer i : rowKeySet) { 312 | int rows = rowMap.get(i); 313 | if (rows < mColumnNum) { 314 | continue; 315 | } 316 | mColumnNum = rows; 317 | } 318 | 319 | Set columnKeySet = columnMap.keySet(); 320 | //遍历取出最大行数 321 | for (Integer i : columnKeySet) { 322 | int columns = columnMap.get(i); 323 | if (columns < mRowNum) { 324 | continue; 325 | } 326 | mRowNum = columns; 327 | } 328 | 329 | } 330 | 331 | /** 332 | * 将view添加进GridLayout 333 | */ 334 | private void addItem() { 335 | for (int i = 0; i < mItemList.size(); i++) { 336 | View childView = mViewList.get(i); 337 | ZoomHoverItem item = mItemList.get(i); 338 | GridLayout.LayoutParams childViewLp = (LayoutParams) childView.getLayoutParams(); 339 | if (childViewLp == null) { 340 | childViewLp = new GridLayout.LayoutParams(); 341 | } 342 | //设置子view的宽高 343 | if (mUseBaseWH) { 344 | childViewLp.width = mBaseWidth * item.getColumnSpan() + (item.getColumnSpan() > 1 ? (item.getColumnSpan() - 1) * mDivider : 0); 345 | childViewLp.height = mBaseHeight * item.getRowSpan() + (item.getRowSpan() > 1 ? (item.getRowSpan() - 1) * mDivider : 0); 346 | } else { 347 | if (childViewLp.width > 0) { 348 | childViewLp.width = childViewLp.width * item.getColumnSpan() + (item.getColumnSpan() > 1 ? (item.getColumnSpan() - 1) * mDivider : 0); 349 | } else { 350 | childViewLp.width = LayoutParams.WRAP_CONTENT; 351 | } 352 | 353 | if (childViewLp.height > 0) { 354 | childViewLp.height = childViewLp.height * item.getRowSpan() + (item.getRowSpan() > 1 ? (item.getRowSpan() - 1) * mDivider : 0); 355 | } else { 356 | childViewLp.height = LayoutParams.WRAP_CONTENT; 357 | } 358 | } 359 | //确定子view的行列位置 360 | childViewLp.rowSpec = GridLayout.spec(item.getRow(), item.getRowSpan()); 361 | childViewLp.columnSpec = GridLayout.spec(item.getColumn(), item.getColumnSpan()); 362 | int right = 0; 363 | int top = 0; 364 | int bottom = 0; 365 | int left = 0; 366 | if (item.getColumn() > 0) { 367 | //除了第一列左边距为分割线的宽度 368 | left = mDivider; 369 | } else if (item.getColumn() == 0) { 370 | //第一列的左边距=距离父布局的边距 371 | left = mMarginParent; 372 | } 373 | 374 | if (item.getRow() > 0) { 375 | //不是第一行上边距为分割线的宽度 376 | top = mDivider; 377 | } else if (item.getRow() == 0) { 378 | //第一行的上边距=距离父布局的边距 379 | top = mMarginParent; 380 | } 381 | 382 | //最后一列的右边距=距离父布局的边距 383 | if (item.getColumn() + item.getColumnSpan() >= mColumnNum) { 384 | right = mMarginParent; 385 | } 386 | 387 | //最后一行的下边距=距离父布局的边距 388 | if (item.getRow() + item.getRowSpan() >= mRowNum) { 389 | bottom = mMarginParent; 390 | } 391 | //设置margin 392 | childViewLp.setMargins(left, top, right, bottom); 393 | addView(childView, childViewLp); 394 | childView.setOnClickListener(this); 395 | } 396 | } 397 | 398 | 399 | @Override 400 | public void onChanged() { 401 | changeAdapter(); 402 | } 403 | 404 | 405 | @Override 406 | public void onClick(View view) { 407 | if (mCurrentView == null) { 408 | //如果currentView为null,证明第一次点击 409 | zoomInAnim(view); 410 | mCurrentView = view; 411 | if (mOnItemSelectedListener != null) { 412 | mOnItemSelectedListener.onItemSelected(mCurrentView, mCurrentView.getId() - 1); 413 | } 414 | } else { 415 | if (view.getId() != mCurrentView.getId()) { 416 | //点击的view不是currentView 417 | //currentView执行缩小动画 418 | zoomOutAnim(mCurrentView); 419 | //当前点击的view赋值给currentView 420 | mCurrentView = view; 421 | //执行放大动画 422 | zoomInAnim(mCurrentView); 423 | if (mOnItemSelectedListener != null) { 424 | mOnItemSelectedListener.onItemSelected(mCurrentView, mCurrentView.getId() - 1); 425 | } 426 | } 427 | } 428 | } 429 | 430 | /** 431 | * 放大动画 432 | * 433 | * @param view 434 | */ 435 | private void zoomInAnim(final View view) { 436 | //将view放在其他view之上 437 | view.bringToFront(); 438 | //按照bringToFront文档来的,暂没测试 439 | if (Build.VERSION.SDK_INT < KITKAT) { 440 | requestLayout(); 441 | } 442 | if (mCurrentZoomInAnim != null) { 443 | //如果当前有放大动画执行,cancel调 444 | mCurrentZoomInAnim.cancel(); 445 | } 446 | ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, mAnimZoomTo); 447 | ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, mAnimZoomTo); 448 | objectAnimatorX.setDuration(mAnimDuration); 449 | objectAnimatorX.setInterpolator(mZoomInInterpolator); 450 | objectAnimatorY.setDuration(mAnimDuration); 451 | objectAnimatorY.setInterpolator(mZoomInInterpolator); 452 | AnimatorSet set = new AnimatorSet(); 453 | set.playTogether(objectAnimatorX, objectAnimatorY); 454 | 455 | set.addListener(new Animator.AnimatorListener() { 456 | @Override 457 | public void onAnimationStart(Animator animator) { 458 | //放大动画开始 459 | if (mOnZoomAnimatorListener != null) { 460 | mOnZoomAnimatorListener.onZoomInStart(view); 461 | } 462 | } 463 | 464 | @Override 465 | public void onAnimationEnd(Animator animator) { 466 | //放大动画结束 467 | if (mOnZoomAnimatorListener != null) { 468 | mOnZoomAnimatorListener.onZoomInEnd(view); 469 | } 470 | mCurrentZoomInAnim = null; 471 | } 472 | 473 | @Override 474 | public void onAnimationCancel(Animator animator) { 475 | //放大动画退出 476 | mCurrentZoomInAnim = null; 477 | } 478 | 479 | @Override 480 | public void onAnimationRepeat(Animator animator) { 481 | 482 | } 483 | }); 484 | set.start(); 485 | mCurrentZoomInAnim = set; 486 | } 487 | 488 | /** 489 | * 缩小动画 490 | * 491 | * @param view 492 | */ 493 | private void zoomOutAnim(final View view) { 494 | if (mCurrentZoomOutAnim != null) { 495 | //如果当前有缩小动画执行,cancel调 496 | mCurrentZoomOutAnim.cancel(); 497 | //动画cancel后,上一个缩小view的scaleX不是1.0,就手动设置scaleX,Y到1.0 498 | if (mPreZoomOutView != null && mPreZoomOutView.getScaleX() > 1.0) { 499 | mPreZoomOutView.setScaleX(1.0f); 500 | mPreZoomOutView.setScaleY(1.0f); 501 | } 502 | } 503 | ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", mAnimZoomTo, 1.0f); 504 | ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", mAnimZoomTo, 1.0f); 505 | objectAnimatorX.setDuration(mAnimDuration); 506 | objectAnimatorX.setInterpolator(mZoomOutInterpolator); 507 | objectAnimatorY.setDuration(mAnimDuration); 508 | objectAnimatorY.setInterpolator(mZoomOutInterpolator); 509 | AnimatorSet set = new AnimatorSet(); 510 | set.playTogether(objectAnimatorX, objectAnimatorY); 511 | 512 | set.addListener(new Animator.AnimatorListener() { 513 | @Override 514 | public void onAnimationStart(Animator animator) { 515 | //缩小动画开始 516 | if (mOnZoomAnimatorListener != null) { 517 | mOnZoomAnimatorListener.onZoomOutStart(view); 518 | } 519 | } 520 | 521 | @Override 522 | public void onAnimationEnd(Animator animator) { 523 | //缩小动画结束 524 | if (mOnZoomAnimatorListener != null) { 525 | mOnZoomAnimatorListener.onZoomOutEnd(view); 526 | } 527 | mCurrentZoomOutAnim = null; 528 | } 529 | 530 | @Override 531 | public void onAnimationCancel(Animator animator) { 532 | //缩小动画退出 533 | mCurrentZoomOutAnim = null; 534 | } 535 | 536 | @Override 537 | public void onAnimationRepeat(Animator animator) { 538 | 539 | } 540 | }); 541 | set.start(); 542 | mCurrentZoomOutAnim = set; 543 | //把当前缩放的view作为上一个缩放的view 544 | mPreZoomOutView = view; 545 | } 546 | 547 | /** 548 | * 设置缩放动画监听器 549 | * 550 | * @param listener 551 | */ 552 | public void setOnZoomAnimatorListener(OnZoomAnimatorListener listener) { 553 | this.mOnZoomAnimatorListener = listener; 554 | } 555 | 556 | /** 557 | * 同时设置插值器 558 | * 559 | * @param interpolator 560 | */ 561 | public void setZoomInterpolator(Interpolator interpolator) { 562 | this.mZoomInInterpolator = this.mZoomOutInterpolator = interpolator; 563 | } 564 | 565 | /** 566 | * 设置放大动画插值器 567 | * 568 | * @param interpolator 569 | */ 570 | public void setZoomInInterpolator(Interpolator interpolator) { 571 | this.mZoomInInterpolator = interpolator; 572 | } 573 | 574 | /** 575 | * 设置缩小动画的插值器 576 | * 577 | * @param interpolator 578 | */ 579 | public void setZoomOutInterpolator(Interpolator interpolator) { 580 | this.mZoomOutInterpolator = interpolator; 581 | } 582 | 583 | /** 584 | * 设置item选中监听器 585 | * 586 | * @param listener 587 | */ 588 | public void setOnItemSelectedListener(OnItemSelectedListener listener) { 589 | this.mOnItemSelectedListener = listener; 590 | } 591 | 592 | /** 593 | * 设置选中的条目 594 | * 595 | * @param position 596 | */ 597 | public void setSelectedItem(int position) { 598 | //list为空或者size map) { 673 | this.mSpanMap.clear(); 674 | this.mSpanMap.putAll(map); 675 | if (mZoomHoverAdapter != null) { 676 | //如果adapter不为null重新布局 677 | changeAdapter(); 678 | } 679 | } 680 | 681 | /** 682 | * 添加跨度item 683 | * 684 | * @param item 685 | */ 686 | public void addSpanItem(int position, ZoomHoverSpan item) { 687 | this.mSpanMap.put(position, item); 688 | if (mZoomHoverAdapter != null) { 689 | //如果adapter不为null重新布局 690 | changeAdapter(); 691 | } 692 | } 693 | 694 | /** 695 | * 添加跨度信息 696 | * 697 | * @param position 698 | * @param rowSpan 699 | * @param columnSpan 700 | */ 701 | public void addSpanItem(int position, int rowSpan, int columnSpan) { 702 | this.mSpanMap.put(position, new ZoomHoverSpan(rowSpan, columnSpan)); 703 | if (mZoomHoverAdapter != null) { 704 | //如果adapter不为null重新布局 705 | changeAdapter(); 706 | } 707 | } 708 | 709 | /** 710 | * 设置是否使用基础宽高 711 | * 712 | * @param useBaseWH 713 | */ 714 | public void setUseBaseWH(boolean useBaseWH) { 715 | this.mUseBaseWH = useBaseWH; 716 | } 717 | 718 | /** 719 | * 设置基础的宽度 720 | * 721 | * @param baseWidth 722 | */ 723 | public void setBaseWidth(int baseWidth) { 724 | this.mBaseWidth = baseWidth; 725 | } 726 | 727 | /** 728 | * 设置基础高度 729 | * 730 | * @param baseHeight 731 | */ 732 | public void setBaseHeight(int baseHeight) { 733 | this.mBaseHeight = baseHeight; 734 | } 735 | 736 | } 737 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverItem.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by zyyoona7 on 2016/8/25. 8 | * 设置span时候需要 9 | */ 10 | 11 | public class ZoomHoverItem implements Parcelable { 12 | 13 | //---row,column确定坐标 14 | //行 15 | private int row; 16 | 17 | //列 18 | private int column; 19 | 20 | //---支持跨行,跨列 21 | //横跨行 22 | private int rowSpan=1; 23 | 24 | //横跨列 25 | private int columnSpan=1; 26 | 27 | public ZoomHoverItem(){ 28 | 29 | } 30 | 31 | public ZoomHoverItem(int rowSpan, int columnSpan) { 32 | this.rowSpan = rowSpan; 33 | this.columnSpan = columnSpan; 34 | } 35 | 36 | public ZoomHoverItem(int row, int column, int rowSpan, int columnSpan) { 37 | this.row = row; 38 | this.column = column; 39 | this.rowSpan = rowSpan; 40 | this.columnSpan = columnSpan; 41 | } 42 | 43 | protected ZoomHoverItem(Parcel in) { 44 | row = in.readInt(); 45 | column = in.readInt(); 46 | rowSpan = in.readInt(); 47 | columnSpan = in.readInt(); 48 | } 49 | 50 | public static final Creator CREATOR = new Creator() { 51 | @Override 52 | public ZoomHoverItem createFromParcel(Parcel in) { 53 | return new ZoomHoverItem(in); 54 | } 55 | 56 | @Override 57 | public ZoomHoverItem[] newArray(int size) { 58 | return new ZoomHoverItem[size]; 59 | } 60 | }; 61 | 62 | public int getColumnSpan() { 63 | return columnSpan; 64 | } 65 | 66 | public void setColumnSpan(int columnSpan) { 67 | this.columnSpan = columnSpan; 68 | } 69 | 70 | public int getRow() { 71 | return row; 72 | } 73 | 74 | public void setRow(int row) { 75 | this.row = row; 76 | } 77 | 78 | public int getColumn() { 79 | return column; 80 | } 81 | 82 | public void setColumn(int column) { 83 | this.column = column; 84 | } 85 | 86 | public int getRowSpan() { 87 | return rowSpan; 88 | } 89 | 90 | public void setRowSpan(int rowSpan) { 91 | this.rowSpan = rowSpan; 92 | } 93 | 94 | @Override 95 | public int describeContents() { 96 | return 0; 97 | } 98 | 99 | @Override 100 | public void writeToParcel(Parcel dest, int flags) { 101 | dest.writeInt(row); 102 | dest.writeInt(column); 103 | dest.writeInt(rowSpan); 104 | dest.writeInt(columnSpan); 105 | } 106 | 107 | @Override 108 | public boolean equals(Object o) { 109 | ZoomHoverItem item= (ZoomHoverItem) o; 110 | return item.getRow()==row && item.getColumn()==column; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverSpan.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | /** 4 | * Created by zyyoona7 on 2016/8/25. 5 | *

6 | * 设置对应的rowSpan,columnSpan 7 | */ 8 | 9 | public class ZoomHoverSpan { 10 | 11 | private int rowSpan; 12 | 13 | private int columnSpan; 14 | 15 | public ZoomHoverSpan() { 16 | } 17 | 18 | public ZoomHoverSpan(int rowSpan, int columnSpan) { 19 | this.rowSpan = rowSpan; 20 | this.columnSpan = columnSpan; 21 | } 22 | 23 | public int getRowSpan() { 24 | return rowSpan; 25 | } 26 | 27 | public void setRowSpan(int rowSpan) { 28 | this.rowSpan = rowSpan; 29 | } 30 | 31 | public int getColumnSpan() { 32 | return columnSpan; 33 | } 34 | 35 | public void setColumnSpan(int columnSpan) { 36 | this.columnSpan = columnSpan; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyyoona7/customviewsets/zoom_hover/ZoomHoverView.java: -------------------------------------------------------------------------------- 1 | package com.zyyoona7.customviewsets.zoom_hover; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Canvas; 9 | import android.os.Build; 10 | import android.support.v4.util.SimpleArrayMap; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.animation.AccelerateDecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | import android.widget.RelativeLayout; 18 | 19 | import com.zyyoona7.customviewsets.R; 20 | import com.zyyoona7.customviewsets.utils.DensityUtils; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import static android.os.Build.VERSION_CODES.KITKAT; 26 | 27 | /** 28 | * Created by zyyoona7 on 2016/8/16. 29 | */ 30 | 31 | public class ZoomHoverView extends RelativeLayout implements View.OnClickListener, ZoomHoverAdapter.OnDataChangedListener { 32 | 33 | private static final String TAG = "ZoomHoverView"; 34 | 35 | //adapter 36 | private ZoomHoverAdapter mZoomHoverAdapter; 37 | 38 | // 需要的列数 39 | private int mColumnNum = 3; 40 | 41 | //记录当前列 42 | private int mCurrentColumn = 0; 43 | 44 | //记录当前行 45 | private int mCurrentRow = 1; 46 | 47 | //记录每行第一列的下标(row First column position) 48 | //K--所在行数 V--当前view的下标 49 | private SimpleArrayMap mRFColPosMap = new SimpleArrayMap<>(); 50 | 51 | //子view距离父控件的外边距宽度 52 | private int mMarginParent = 20; 53 | 54 | //行列的分割线宽度 55 | private int mDivider = 10; 56 | 57 | //当前放大动画 58 | private AnimatorSet mCurrentZoomInAnim = null; 59 | 60 | //当前缩小动画 61 | private AnimatorSet mCurrentZoomOutAnim = null; 62 | 63 | //缩放动画监听器 64 | private OnZoomAnimatorListener mOnZoomAnimatorListener = null; 65 | 66 | //动画持续时间 67 | private int mAnimDuration; 68 | 69 | //动画缩放倍数 70 | private float mAnimZoomTo; 71 | 72 | //缩放动画插值器 73 | private Interpolator mZoomInInterpolator; 74 | private Interpolator mZoomOutInterpolator; 75 | 76 | //上一个ZoomOut的view(为了解决快速切换时,上一个被缩小的view缩放大小不正常的情况) 77 | private View mPreZoomOutView; 78 | 79 | //当前被选中的view 80 | private View mCurrentView = null; 81 | 82 | //item选中监听器 83 | private OnItemSelectedListener mOnItemSelectedListener; 84 | 85 | //存储当前layout中所有子view 86 | private List mViewList; 87 | 88 | //需要横跨的map 89 | private SimpleArrayMap mNeedSpanMap = new SimpleArrayMap<>(); 90 | 91 | public ZoomHoverView(Context context) { 92 | this(context, null); 93 | } 94 | 95 | public ZoomHoverView(Context context, AttributeSet attrs) { 96 | super(context, attrs); 97 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZoomHoverView); 98 | mDivider = typedArray.getDimensionPixelOffset(R.styleable.ZoomHoverView_zhv_divider, DensityUtils.dip2px(context, 5)); 99 | mMarginParent = typedArray.getDimensionPixelOffset(R.styleable.ZoomHoverView_zhv_margin_parent, DensityUtils.dip2px(context, 5)); 100 | mColumnNum = typedArray.getInt(R.styleable.ZoomHoverView_zhv_column_num, 3); 101 | mAnimDuration = typedArray.getInt(R.styleable.ZoomHoverView_zhv_zoom_duration, getResources().getInteger(android.R.integer.config_shortAnimTime)); 102 | mAnimZoomTo = typedArray.getFloat(R.styleable.ZoomHoverView_zhv_zoom_to, 1.2f); 103 | typedArray.recycle(); 104 | mZoomOutInterpolator = mZoomInInterpolator = new AccelerateDecelerateInterpolator(); 105 | } 106 | 107 | @Override 108 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 109 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 110 | 111 | Log.e(TAG, "onMeasure: 执行了..." + getWidth() + "," + getHeight()); 112 | } 113 | 114 | @Override 115 | protected void onDraw(Canvas canvas) { 116 | super.onDraw(canvas); 117 | Log.e(TAG, "onDraw: 执行了..."); 118 | } 119 | 120 | /** 121 | * 设置适配器 122 | * 123 | * @param adapter 124 | */ 125 | 126 | public void setAdapter(ZoomHoverAdapter adapter) { 127 | this.mZoomHoverAdapter = adapter; 128 | mZoomHoverAdapter.setDataChangedListener(this); 129 | changeAdapter(); 130 | } 131 | 132 | 133 | /** 134 | * 根据adapter添加view 135 | */ 136 | private void changeAdapter() { 137 | removeAllViews(); 138 | //重置参数(因为changeAdapter可能调用多次) 139 | mColumnNum = 3; 140 | mCurrentRow = 1; 141 | mCurrentColumn = 0; 142 | mRFColPosMap.clear(); 143 | requestLayout(); 144 | mViewList = new ArrayList<>(mZoomHoverAdapter.getCount()); 145 | //需要拉伸的下标的参数K-下标,V-跨度 146 | SimpleArrayMap needSpanMap = mNeedSpanMap; 147 | for (int i = 0; i < mZoomHoverAdapter.getCount(); i++) { 148 | //获取子view 149 | View childView = mZoomHoverAdapter.getView(this, i, mZoomHoverAdapter.getItem(i)); 150 | mViewList.add(childView); 151 | childView.setId(i + 1); 152 | 153 | //判断当前view是否设置了跨度 154 | int span = 1; 155 | if (needSpanMap.containsKey(i)) { 156 | span = needSpanMap.get(i); 157 | } 158 | 159 | //获取AdapterView的的布局参数 160 | RelativeLayout.LayoutParams childViewParams = (LayoutParams) childView.getLayoutParams(); 161 | 162 | if (childViewParams == null) { 163 | childViewParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 164 | } 165 | 166 | //如果view的宽高设置了wrap_content或者match_parent则span无效 167 | if (childViewParams.width <= 0) { 168 | span = 1; 169 | } 170 | 171 | //如果跨度有变,重新设置view的宽 172 | if (span > 1 && span <= mColumnNum) { 173 | childViewParams.width = childViewParams.width * span + (span - 1) * mDivider; 174 | } else if (span < 1) { 175 | span = 1; 176 | } else if (span > mColumnNum) { 177 | span = mColumnNum; 178 | childViewParams.width = childViewParams.width * span + (span - 1) * mDivider; 179 | } 180 | 181 | //设置右下左上的边距 182 | int rightMargin = 0; 183 | int bottomMargin = 0; 184 | int leftMargin = 0; 185 | int topMargin = 0; 186 | 187 | //如果跨度+当前的列>设置的列数,换行 188 | if (span + mCurrentColumn > mColumnNum) { 189 | //换行当前行数+1 190 | mCurrentRow++; 191 | //当前列等于当前view的跨度 192 | mCurrentColumn = span; 193 | //换行以后肯定是第一个 194 | mRFColPosMap.put(mCurrentRow, i); 195 | //换行操作 196 | //因为换行,肯定不是第一行 197 | //换行操作后将当前view添加到上一行第一个位置的下面 198 | childViewParams.addRule(RelativeLayout.BELOW, 199 | mViewList.get(mRFColPosMap.get(mCurrentRow - 1)).getId()); 200 | //不是第一行,所以上边距为分割线的宽度 201 | topMargin = mDivider; 202 | //换行后位置在左边第一个,所以左边距为距离父控件的边距 203 | leftMargin = mMarginParent; 204 | } else { 205 | if (mCurrentColumn <= 0 && mCurrentRow <= 1) { 206 | //第一行第一列的位置保存第一列信息,同时第一列不需要任何相对规则 207 | mRFColPosMap.put(mCurrentRow, i); 208 | //第一行第一列上边距和左边距都是距离父控件的边距 209 | topMargin = mMarginParent; 210 | leftMargin = mMarginParent; 211 | } else { 212 | //不是每一行的第一个,就添加到前一个的view的右面,并且和前一个顶部对齐 213 | childViewParams.addRule(RelativeLayout.RIGHT_OF, 214 | mViewList.get(i - 1).getId()); 215 | childViewParams.addRule(ALIGN_TOP, mViewList.get(i - 1).getId()); 216 | 217 | } 218 | //移动到当前列 219 | mCurrentColumn += span; 220 | } 221 | 222 | if (mCurrentColumn >= mColumnNum || i >= mZoomHoverAdapter.getCount() - 1) { 223 | //如果当前列为列总数或者当前view的下标等于最后一个view的下标那么就是最右边的view,设置父边距 224 | rightMargin = mMarginParent; 225 | } else { 226 | rightMargin = mDivider; 227 | } 228 | 229 | //如果当前view是最后一个那么他肯定是最后一行 230 | if (i >= (mZoomHoverAdapter.getCount() - 1)) { 231 | bottomMargin = mMarginParent; 232 | } 233 | 234 | //设置外边距 235 | childViewParams.setMargins(leftMargin, topMargin, rightMargin, 236 | bottomMargin); 237 | //添加view 238 | addView(childView, childViewParams); 239 | //添加点击事件 240 | childView.setOnClickListener(this); 241 | } 242 | } 243 | 244 | 245 | @Override 246 | public void onChanged() { 247 | changeAdapter(); 248 | } 249 | 250 | 251 | @Override 252 | public void onClick(View view) { 253 | if (mCurrentView == null) { 254 | //如果currentView为null,证明第一次点击 255 | zoomInAnim(view); 256 | mCurrentView = view; 257 | if (mOnItemSelectedListener != null) { 258 | mOnItemSelectedListener.onItemSelected(mCurrentView, mCurrentView.getId() - 1); 259 | } 260 | } else { 261 | if (view.getId() != mCurrentView.getId()) { 262 | //点击的view不是currentView 263 | //currentView执行缩小动画 264 | zoomOutAnim(mCurrentView); 265 | //当前点击的view赋值给currentView 266 | mCurrentView = view; 267 | //执行放大动画 268 | zoomInAnim(mCurrentView); 269 | if (mOnItemSelectedListener != null) { 270 | mOnItemSelectedListener.onItemSelected(mCurrentView, mCurrentView.getId() - 1); 271 | } 272 | } 273 | } 274 | } 275 | 276 | /** 277 | * 放大动画 278 | * 279 | * @param view 280 | */ 281 | private void zoomInAnim(final View view) { 282 | //将view放在其他view之上 283 | view.bringToFront(); 284 | //按照bringToFront文档来的,暂没测试 285 | if (Build.VERSION.SDK_INT < KITKAT) { 286 | requestLayout(); 287 | } 288 | if (mCurrentZoomInAnim != null) { 289 | //如果当前有放大动画执行,cancel调 290 | mCurrentZoomInAnim.cancel(); 291 | } 292 | ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, mAnimZoomTo); 293 | ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, mAnimZoomTo); 294 | objectAnimatorX.setDuration(mAnimDuration); 295 | objectAnimatorX.setInterpolator(mZoomInInterpolator); 296 | objectAnimatorY.setDuration(mAnimDuration); 297 | objectAnimatorY.setInterpolator(mZoomInInterpolator); 298 | AnimatorSet set = new AnimatorSet(); 299 | set.playTogether(objectAnimatorX, objectAnimatorY); 300 | 301 | set.addListener(new Animator.AnimatorListener() { 302 | @Override 303 | public void onAnimationStart(Animator animator) { 304 | //放大动画开始 305 | if (mOnZoomAnimatorListener != null) { 306 | mOnZoomAnimatorListener.onZoomInStart(view); 307 | } 308 | } 309 | 310 | @Override 311 | public void onAnimationEnd(Animator animator) { 312 | //放大动画结束 313 | if (mOnZoomAnimatorListener != null) { 314 | mOnZoomAnimatorListener.onZoomInEnd(view); 315 | } 316 | mCurrentZoomInAnim = null; 317 | } 318 | 319 | @Override 320 | public void onAnimationCancel(Animator animator) { 321 | //放大动画退出 322 | mCurrentZoomInAnim = null; 323 | } 324 | 325 | @Override 326 | public void onAnimationRepeat(Animator animator) { 327 | 328 | } 329 | }); 330 | set.start(); 331 | mCurrentZoomInAnim = set; 332 | } 333 | 334 | /** 335 | * 缩小动画 336 | * 337 | * @param view 338 | */ 339 | private void zoomOutAnim(final View view) { 340 | if (mCurrentZoomOutAnim != null) { 341 | //如果当前有缩小动画执行,cancel调 342 | mCurrentZoomOutAnim.cancel(); 343 | //动画cancel后,上一个缩小view的scaleX不是1.0,就手动设置scaleX,Y到1.0 344 | if (mPreZoomOutView != null && mPreZoomOutView.getScaleX() > 1.0) { 345 | mPreZoomOutView.setScaleX(1.0f); 346 | mPreZoomOutView.setScaleY(1.0f); 347 | } 348 | } 349 | ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", mAnimZoomTo, 1.0f); 350 | ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", mAnimZoomTo, 1.0f); 351 | objectAnimatorX.setDuration(mAnimDuration); 352 | objectAnimatorX.setInterpolator(mZoomOutInterpolator); 353 | objectAnimatorY.setDuration(mAnimDuration); 354 | objectAnimatorY.setInterpolator(mZoomOutInterpolator); 355 | AnimatorSet set = new AnimatorSet(); 356 | set.playTogether(objectAnimatorX, objectAnimatorY); 357 | 358 | set.addListener(new Animator.AnimatorListener() { 359 | @Override 360 | public void onAnimationStart(Animator animator) { 361 | //缩小动画开始 362 | if (mOnZoomAnimatorListener != null) { 363 | mOnZoomAnimatorListener.onZoomOutStart(view); 364 | } 365 | } 366 | 367 | @Override 368 | public void onAnimationEnd(Animator animator) { 369 | //缩小动画结束 370 | if (mOnZoomAnimatorListener != null) { 371 | mOnZoomAnimatorListener.onZoomOutEnd(view); 372 | } 373 | mCurrentZoomOutAnim = null; 374 | } 375 | 376 | @Override 377 | public void onAnimationCancel(Animator animator) { 378 | //缩小动画退出 379 | mCurrentZoomOutAnim = null; 380 | } 381 | 382 | @Override 383 | public void onAnimationRepeat(Animator animator) { 384 | 385 | } 386 | }); 387 | set.start(); 388 | mCurrentZoomOutAnim = set; 389 | //把当前缩放的view作为上一个缩放的view 390 | mPreZoomOutView = view; 391 | } 392 | 393 | /** 394 | * 设置缩放动画监听器 395 | * 396 | * @param listener 397 | */ 398 | public void setOnZoomAnimatorListener(OnZoomAnimatorListener listener) { 399 | this.mOnZoomAnimatorListener = listener; 400 | } 401 | 402 | /** 403 | * 同时设置插值器 404 | * 405 | * @param interpolator 406 | */ 407 | public void setZoomInterpolator(Interpolator interpolator) { 408 | this.mZoomInInterpolator = this.mZoomOutInterpolator = interpolator; 409 | } 410 | 411 | /** 412 | * 设置放大动画插值器 413 | * 414 | * @param interpolator 415 | */ 416 | public void setZoomInInterpolator(Interpolator interpolator) { 417 | this.mZoomInInterpolator = interpolator; 418 | } 419 | 420 | /** 421 | * 设置缩小动画的插值器 422 | * 423 | * @param interpolator 424 | */ 425 | public void setZoomOutInterpolator(Interpolator interpolator) { 426 | this.mZoomOutInterpolator = interpolator; 427 | } 428 | 429 | /** 430 | * 设置item选中监听器 431 | * 432 | * @param listener 433 | */ 434 | public void setOnItemSelectedListener(OnItemSelectedListener listener) { 435 | this.mOnItemSelectedListener = listener; 436 | } 437 | 438 | /** 439 | * 设置选中的条目 440 | * 441 | * @param position 442 | */ 443 | public void setSelectedItem(int position) { 444 | //list为空或者size map) { 520 | this.mNeedSpanMap.clear(); 521 | this.mNeedSpanMap.putAll(map); 522 | if (this.mZoomHoverAdapter != null) { 523 | changeAdapter(); 524 | } 525 | } 526 | 527 | public void addSpan(int position, int span) { 528 | this.mNeedSpanMap.clear(); 529 | this.mNeedSpanMap.put(position, span); 530 | if (this.mZoomHoverAdapter != null) { 531 | changeAdapter(); 532 | } 533 | } 534 | 535 | } 536 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/heart_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/heart_default.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ss_heart1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/ss_heart1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ss_heart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/ss_heart2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ss_heart3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/ss_heart3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ss_heart4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/ss_heart4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ss_heart5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyyoona7/CustomViewSets/6afe3a792ebe2b6c11601fc2b352170204ac9baf/app/src/main/res/drawable-xxhdpi/ss_heart5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/shadow_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_basic_operation.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 19 | 20 |