├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── compiler.xml ├── encodings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app-debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── anbetter │ │ └── xplayer │ │ ├── App.java │ │ ├── LiveDemoActivity.java │ │ ├── LiveListActivity.java │ │ ├── LiveRoomActivity.java │ │ ├── MainActivity.java │ │ ├── ScrollingActivity.java │ │ ├── VideoListAdapter.java │ │ ├── XPlayerActivity.java │ │ ├── discretescrollview │ │ ├── DSVOrientation.java │ │ ├── Direction.java │ │ ├── DiscreteScrollLayoutManager.java │ │ ├── DiscreteScrollView.java │ │ ├── InfiniteScrollAdapter.java │ │ ├── RecyclerViewProxy.java │ │ ├── transform │ │ │ ├── DiscreteScrollItemTransformer.java │ │ │ ├── Pivot.java │ │ │ └── ScaleTransformer.java │ │ └── util │ │ │ └── ScrollListenerAdapter.java │ │ └── model │ │ └── VideoInfo.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ ├── default_play_img.png │ ├── default_service_video_dot.png │ ├── default_service_video_play_xiao.png │ ├── default_service_video_suspend.png │ └── default_service_xuanzhuan.png │ ├── drawable-xxhdpi │ ├── default_play_img.png │ ├── default_service_video_dot.png │ ├── default_service_video_play_xiao.png │ ├── default_service_video_suspend.png │ └── default_service_xuanzhuan.png │ ├── drawable │ ├── default_seekbar.xml │ ├── default_seekbar_thumb.xml │ ├── ic_launcher_background.xml │ └── shape_do_bg.xml │ ├── layout │ ├── activity_live_demo.xml │ ├── activity_live_list.xml │ ├── activity_live_room.xml │ ├── activity_main.xml │ ├── activity_player.xml │ ├── activity_scrolling.xml │ └── live_list_item.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── demo_01.jpeg ├── demo_02.jpeg ├── demo_03.jpeg ├── demo_04.jpeg ├── demo_05.jpeg └── demo_06.jpeg ├── settings.gradle └── xplayer_ijk ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── anbetter │ └── xplayer │ └── ijk │ ├── TextureRenderView.java │ ├── XDragVideoView.java │ ├── XMediaPlayer.java │ ├── XVideoControls.java │ ├── XVideoView.java │ ├── api │ ├── IXMediaPlayer.java │ ├── IXRenderView.java │ └── IXVideoView.java │ ├── delegate │ └── XMediaPlayerDelegate.java │ ├── listener │ ├── IXVideoViewListener.java │ ├── XMediaPlayerAdapterListener.java │ └── XMediaPlayerListener.java │ └── utils │ ├── OrientationEventUtils.java │ ├── ViewMeasureUtils.java │ └── XUtils.java └── res ├── drawable-xhdpi ├── btn_small_close_normal.png ├── btn_small_close_pressed.png ├── jz_add_volume.png ├── jz_back_normal.png ├── jz_back_pressed.png ├── jz_back_tiny_normal.png ├── jz_back_tiny_pressed.png ├── jz_backward_icon.png ├── jz_battery_level_10.png ├── jz_battery_level_100.png ├── jz_battery_level_30.png ├── jz_battery_level_50.png ├── jz_battery_level_70.png ├── jz_battery_level_90.png ├── jz_brightness_video.png ├── jz_clarity_popwindow_bg.9.png ├── jz_close_volume.png ├── jz_enlarge.png ├── jz_forward_icon.png ├── jz_loading_bg.png ├── jz_pause_normal.png ├── jz_pause_pressed.png ├── jz_play_normal.png ├── jz_play_pressed.png ├── jz_restart_normal.png ├── jz_restart_pressed.png ├── jz_share_normal.png ├── jz_share_pressed.png ├── jz_shrink.png └── jz_volume_icon.png ├── drawable ├── btn_small_close_selector.xml ├── jz_click_pause_selector.xml ├── jz_click_play_selector.xml └── jz_click_replay_selector.xml ├── layout └── video_view_controls_layout.xml └── values ├── ids.xml └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | # *.apk 3 | # *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/assetWizardSettings.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | .idea/caches 44 | 45 | # Keystore files 46 | # Uncomment the following line if you do not want to check your keystore files in. 47 | *.jks 48 | 49 | # External native build folder generated in Android Studio 2.2 and later 50 | .externalNativeBuild 51 | 52 | # Google Services (e.g. APIs or Firebase) 53 | google-services.json 54 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 25 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.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. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XPlayer2 2 | 基于ijkPlayer的Android视频播放器 3 | 4 | ## 示例apk的安装 5 | adb install -t *.apk 6 | (adb install -t /Users/android_ls/Desktop/app-debug.apk) 7 | 8 | ## 视频播放器组件提供的基本功能 9 | - 支持播放直播流(rtmp) 10 | - 支持播放各种格式的多媒体(MP4、flv、MP3) 11 | - 支持多种视频画面显示方式:16:9、4:3、全屏、竖直居中等 12 | - 支持常用的动画(旋转、移动、缩放) 13 | - 支持倍速播放(加快或者减慢播放速度) 14 | 15 | ## 特性 16 | - 列表中所有XIVideoView对象共享同一个XIMediaPlayer播放器对象,实现思路: 17 | 1.列表中所有XIVideoView对象共享同一个XIMediaPlayer播放器对象 18 | 2.从之前正在播放的卡片到新的将要播放的卡片,XIMediaPlayer播放器对象做的动作依次是stop、reset、 19 | setVideoPath、setSurface、play 20 | 3.XIMediaPlayer播放器对象创建非常消耗资源,所以推荐尽可能的共享 21 | 22 | - 大小视频窗口切换,实现思路: 23 | 1.采用多个XIVideoView共享同一个XIMediaPlayer播放器对象 24 | 2.在滚动结束后,为XIMediaPlayer对象设置当前显示XIVideoView所持有的Surface 25 | 3.XIMediaPlayer播放器播放视频的动作从未停止,只是切换展示视频画面的Surface而已,从而实现无缝切换大小窗口的功能 26 | 4.小窗口支持在屏幕任意位置拖动,在拖动过程中播放动作不中断。 27 | 28 | ## 示例Demo运行后的截图 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app-debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "com.youzu.xplayer" 7 | minSdkVersion 18 8 | targetSdkVersion 23 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | ndk { 13 | abiFilters "armeabi-v7a" 14 | } 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | // 强制改变所有引用com.android.support版本为27.1.0。 26 | configurations.all { 27 | resolutionStrategy.eachDependency { DependencyResolveDetails details -> 28 | def requested = details.requested 29 | if (requested.group == 'com.android.support') { 30 | if (!requested.name.startsWith("multidex")) { 31 | details.useVersion '27.1.0' 32 | } 33 | } 34 | } 35 | } 36 | 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(include: ['*.jar'], dir: 'libs') 41 | implementation project(":xplayer_ijk") 42 | 43 | implementation 'com.facebook.fresco.helper:fresco-helper:2.0.5' 44 | implementation 'com.android.support:appcompat-v7:26.1.0' 45 | implementation 'com.android.support:design:26.1.0' 46 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 33 | 34 | 38 | 39 | 44 | 45 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/App.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.app.Application; 4 | 5 | import com.anbetter.log.MLog; 6 | import com.facebook.fresco.helper.Phoenix; 7 | 8 | /** 9 | *

10 | * Created by android_ls on 2018/4/25. 11 | * 12 | * @author android_ls 13 | * @version 1.0 14 | */ 15 | public class App extends Application { 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | 21 | Phoenix.init(this); 22 | MLog.init(true, "MLog"); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/LiveDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.anbetter.xplayer.ijk.api.IXRenderView; 8 | import com.anbetter.xplayer.ijk.api.IXVideoView; 9 | 10 | /** 11 | * 视频播放器组件提供的基本功能示例Demo 12 | * 功能: 13 | * 1、支持播放直播流(rtmp) 14 | * 2、支持播放各种格式的多媒体(MP4、flv、MP3) 15 | * 3、支持多种视频画面显示方式:16:9、4:3、全屏、竖直居中等 16 | * 4、支持常用的动画(旋转、移动、缩放) 17 | * 5、支持倍速播放(加快或者减慢播放速度) 18 | *

19 | * Created by android_ls on 2018/4/23. 20 | * 21 | * @author android_ls 22 | * @version 1.0 23 | */ 24 | public class LiveDemoActivity extends AppCompatActivity { 25 | 26 | private IXVideoView mVideoView; 27 | private int mRotation; 28 | private boolean isPause; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_live_demo); 34 | 35 | // String videoPath = "rtmp://live.hkstv.hk.lxdns.com/live/hks"; 36 | String videoPath = "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f30.mp4"; 37 | // String videoPath = "http://ips.ifeng.com/video19.ifeng.com/video09/2014/06/16/1989823-102-086-0009.mp4"; 38 | // String videoPath = "http://jzvd.nathen.cn/1b61da23555d4ce28c805ea303711aa5/7a33ac2af276441bb4b9838f32d8d710-5287d2089db37e62345123a1be272f8b.mp4"; 39 | 40 | 41 | mVideoView = findViewById(R.id.player_view); 42 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FIT_PARENT); 43 | // mVideoView.setDisplayAspectRatio(XIRenderView.AR_MATCH_PARENT); 44 | mVideoView.setVideoPath(videoPath); 45 | 46 | 47 | findViewById(R.id.btn_play).setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View v) { 50 | mVideoView.play(); 51 | } 52 | }); 53 | findViewById(R.id.btn_pause).setOnClickListener(new View.OnClickListener() { 54 | @Override 55 | public void onClick(View v) { 56 | mVideoView.pause(); 57 | } 58 | }); 59 | 60 | findViewById(R.id.btn_resume).setOnClickListener(new View.OnClickListener() { 61 | @Override 62 | public void onClick(View v) { 63 | mVideoView.resume(); 64 | } 65 | }); 66 | 67 | findViewById(R.id.btn_01).setOnClickListener(new View.OnClickListener() { 68 | @Override 69 | public void onClick(View v) { 70 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FILL_PARENT); 71 | } 72 | }); 73 | findViewById(R.id.btn_02).setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View v) { 76 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FIT_PARENT); 77 | } 78 | }); 79 | 80 | findViewById(R.id.btn_04).setOnClickListener(new View.OnClickListener() { 81 | @Override 82 | public void onClick(View v) { 83 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_4_3_FIT_PARENT); 84 | } 85 | }); 86 | findViewById(R.id.btn_05).setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View v) { 89 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_16_9_FIT_PARENT); 90 | } 91 | }); 92 | findViewById(R.id.btn_06).setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View v) { 95 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_MATCH_PARENT); 96 | } 97 | }); 98 | 99 | findViewById(R.id.btn_speed_01).setOnClickListener(new View.OnClickListener() { 100 | @Override 101 | public void onClick(View v) { 102 | mVideoView.setSpeed(0.25f); 103 | } 104 | }); 105 | findViewById(R.id.btn_speed_02).setOnClickListener(new View.OnClickListener() { 106 | @Override 107 | public void onClick(View v) { 108 | mVideoView.setSpeed(0.5f); 109 | } 110 | }); 111 | findViewById(R.id.btn_speed_03).setOnClickListener(new View.OnClickListener() { 112 | @Override 113 | public void onClick(View v) { 114 | mVideoView.setSpeed(1.0f); 115 | } 116 | }); 117 | findViewById(R.id.btn_speed_04).setOnClickListener(new View.OnClickListener() { 118 | @Override 119 | public void onClick(View v) { 120 | mVideoView.setSpeed(1.5f); 121 | } 122 | }); 123 | findViewById(R.id.btn_speed_05).setOnClickListener(new View.OnClickListener() { 124 | @Override 125 | public void onClick(View v) { 126 | mVideoView.setSpeed(2.0f); 127 | } 128 | }); 129 | 130 | findViewById(R.id.btn_rotation_90).setOnClickListener(new View.OnClickListener() { 131 | @Override 132 | public void onClick(View v) { 133 | mRotation += 90; 134 | mVideoView.setVideoRotation(mRotation); 135 | 136 | if (mRotation == 360) { 137 | mRotation = 0; 138 | } 139 | } 140 | }); 141 | 142 | } 143 | 144 | @Override 145 | protected void onPause() { 146 | super.onPause(); 147 | if (mVideoView != null) { 148 | mVideoView.pause(); 149 | isPause = true; 150 | } 151 | } 152 | 153 | @Override 154 | protected void onResume() { 155 | super.onResume(); 156 | if (!isPause) { 157 | return; 158 | } 159 | 160 | if (mVideoView != null) { 161 | mVideoView.resume(); 162 | } 163 | } 164 | 165 | @Override 166 | protected void onDestroy() { 167 | if (mVideoView != null) { 168 | mVideoView.release(); 169 | } 170 | super.onDestroy(); 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/LiveListActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.RecyclerView; 9 | 10 | import com.anbetter.log.MLog; 11 | import com.anbetter.xplayer.discretescrollview.DSVOrientation; 12 | import com.anbetter.xplayer.discretescrollview.DiscreteScrollView; 13 | import com.anbetter.xplayer.discretescrollview.transform.ScaleTransformer; 14 | import com.anbetter.xplayer.ijk.delegate.XMediaPlayerDelegate; 15 | import com.anbetter.xplayer.model.VideoInfo; 16 | 17 | import java.util.ArrayList; 18 | 19 | /** 20 | * 直播列表示例Demo 21 | * 实现思路: 22 | * 1、列表中所有XIVideoView对象共享同一个XIMediaPlayer播放器对象 23 | * 2、从之前正在播放的卡片到新的将要播放的卡片,XIMediaPlayer播放器对象做的动作依次是stop、reset、 24 | * setVideoPath、setSurface、play 25 | * 3、XIMediaPlayer播放器对象创建非常消耗资源,所以推荐尽可能的共享 26 | *

27 | * Created by android_ls on 2018/4/23. 28 | * 29 | * @author android_ls 30 | * @version 1.0 31 | */ 32 | public class LiveListActivity extends AppCompatActivity 33 | implements DiscreteScrollView.OnItemChangedListener { 34 | 35 | String[] imageUrls = { 36 | "https://ws1.sinaimg.cn/large/610dc034ly1fjfae1hjslj20u00tyq4x.jpg", 37 | "http://ww1.sinaimg.cn/large/610dc034ly1fjaxhky81vj20u00u0ta1.jpg", 38 | "https://ws1.sinaimg.cn/large/610dc034ly1fivohbbwlqj20u011idmx.jpg", 39 | "https://ws1.sinaimg.cn/large/610dc034ly1fj78mpyvubj20u011idjg.jpg", 40 | "https://ws1.sinaimg.cn/large/610dc034ly1fj3w0emfcbj20u011iabm.jpg", 41 | "https://ws1.sinaimg.cn/large/610dc034ly1fiz4ar9pq8j20u010xtbk.jpg", 42 | "https://ws1.sinaimg.cn/large/610dc034ly1fis7dvesn6j20u00u0jt4.jpg", 43 | "https://ws1.sinaimg.cn/large/610dc034ly1fir1jbpod5j20ip0newh3.jpg", 44 | "https://ws1.sinaimg.cn/large/610dc034ly1fik2q1k3noj20u00u07wh.jpg", 45 | "https://ws1.sinaimg.cn/large/610dc034ly1fiednrydq8j20u011itfz.jpg" 46 | }; 47 | String[] videoUrls = { 48 | "http://down.fodizi.com/05/d4267-11.flv", 49 | "rtmp://live.hkstv.hk.lxdns.com/live/hks", 50 | "http://jzvd.nathen.cn/1b61da23555d4ce28c805ea303711aa5/7a33ac2af276441bb4b9838f32d8d710-5287d2089db37e62345123a1be272f8b.mp4", 51 | "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f30.mp4", 52 | "http://ips.ifeng.com/video19.ifeng.com/video09/2014/06/16/1989823-102-086-0009.mp4", 53 | "http://mp4.vjshi.com/2016-12-22/e54d476ad49891bd1adda49280a20692.mp4" 54 | }; 55 | 56 | private DiscreteScrollView mDsvLiveList; 57 | private ArrayList mList; 58 | private VideoListAdapter mAdapter; 59 | 60 | private int centerPos = -1; 61 | private boolean isPause; 62 | private Handler mHandler; 63 | 64 | @Override 65 | protected void onCreate(Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | setContentView(R.layout.activity_live_list); 68 | mHandler = new Handler(Looper.getMainLooper()); 69 | 70 | mList = new ArrayList<>(); 71 | for (int i = 0; i < videoUrls.length; i++) { 72 | VideoInfo videoInfo = new VideoInfo(); 73 | videoInfo.coverUrl = imageUrls[i]; 74 | String videoUrl = videoUrls[i]; 75 | videoInfo.videoUrl = videoUrl; 76 | 77 | if (videoUrl.startsWith("rtmp://")) { 78 | videoInfo.desc = "rtmp"; 79 | } else { 80 | videoInfo.desc = videoUrl.substring(videoUrl.lastIndexOf(".")); 81 | } 82 | 83 | mList.add(videoInfo); 84 | } 85 | 86 | mDsvLiveList = findViewById(R.id.dsv_live_list); 87 | mDsvLiveList.setOrientation(DSVOrientation.HORIZONTAL); 88 | mDsvLiveList.setItemTransformer(new ScaleTransformer.Builder() 89 | .setMinScale(0.80f) 90 | .build()); 91 | 92 | mDsvLiveList.setItemTransitionTimeMillis(150); 93 | mDsvLiveList.addOnItemChangedListener(this); 94 | mDsvLiveList.setHasFixedSize(true); 95 | 96 | mAdapter = new VideoListAdapter(mList); 97 | mDsvLiveList.setAdapter(mAdapter); 98 | mDsvLiveList.setOffscreenItems(mList.size()); 99 | 100 | // mDsvLiveList.setSlideOnFlingThreshold(3000); 101 | // mDsvLiveList.setClampTransformProgressAfter(n); 102 | // mDsvLiveList.setSlideOnFling(true); 103 | 104 | } 105 | 106 | @Override 107 | public void onCurrentItemChanged(@Nullable RecyclerView.ViewHolder viewHolder, int adapterPosition) { 108 | if (centerPos != adapterPosition) { 109 | if (centerPos != -1) { 110 | RecyclerView.ViewHolder videoHolder = mDsvLiveList.findViewHolderForAdapterPosition(centerPos); 111 | if (videoHolder != null && videoHolder instanceof VideoListAdapter.VideoPlayerHolder) { 112 | MLog.i("---------stop-----------"); 113 | ((VideoListAdapter.VideoPlayerHolder) videoHolder).stop(); 114 | } 115 | } 116 | 117 | RecyclerView.ViewHolder videoHolder = mDsvLiveList.findViewHolderForAdapterPosition(mDsvLiveList.getCurrentItem()); 118 | if (videoHolder != null && videoHolder instanceof VideoListAdapter.VideoPlayerHolder) { 119 | MLog.i("---------play-----------"); 120 | ((VideoListAdapter.VideoPlayerHolder) videoHolder).play(); 121 | } 122 | 123 | centerPos = adapterPosition; 124 | } 125 | } 126 | 127 | @Override 128 | protected void onPause() { 129 | super.onPause(); 130 | 131 | if (centerPos != -1) { 132 | RecyclerView.ViewHolder videoHolder = mDsvLiveList.findViewHolderForAdapterPosition(centerPos); 133 | if (videoHolder != null && videoHolder instanceof VideoListAdapter.VideoPlayerHolder) { 134 | MLog.i("-----onPause----stop-----------"); 135 | ((VideoListAdapter.VideoPlayerHolder) videoHolder).stop(); 136 | isPause = true; 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | protected void onResume() { 143 | super.onResume(); 144 | if (!isPause) { 145 | return; 146 | } 147 | 148 | mHandler.postDelayed(new Runnable() { 149 | @Override 150 | public void run() { 151 | if (centerPos == -1) { 152 | return; 153 | } 154 | 155 | RecyclerView.ViewHolder videoHolder = mDsvLiveList.findViewHolderForAdapterPosition(centerPos); 156 | if (videoHolder != null && videoHolder instanceof VideoListAdapter.VideoPlayerHolder) { 157 | MLog.i("-----onResume----play-----------"); 158 | ((VideoListAdapter.VideoPlayerHolder) videoHolder).play(); 159 | isPause = false; 160 | } 161 | } 162 | }, 300); 163 | } 164 | 165 | @Override 166 | protected void onDestroy() { 167 | if (mHandler != null) { 168 | mHandler.removeCallbacksAndMessages(null); 169 | } 170 | 171 | MLog.i("-----onDestroy------release--------"); 172 | XMediaPlayerDelegate.getInstance().destroy(); 173 | super.onDestroy(); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/LiveRoomActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.animation.PropertyValuesHolder; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.text.TextUtils; 11 | import android.view.View; 12 | import android.view.Window; 13 | import android.view.WindowManager; 14 | 15 | import com.anbetter.xplayer.ijk.api.IXRenderView; 16 | import com.anbetter.xplayer.ijk.api.IXVideoView; 17 | 18 | /** 19 | * 直播间示例Demo 20 | *

21 | * Created by android_ls on 2018/4/24. 22 | * 23 | * @author android_ls 24 | * @version 1.0 25 | */ 26 | public class LiveRoomActivity extends AppCompatActivity { 27 | 28 | private IXVideoView mVideoView; 29 | private float scaleRatio = 1.0F; // 缩放比例 30 | private ObjectAnimator scaleAnimator; 31 | private boolean isPause; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | requestWindowFeature(Window.FEATURE_NO_TITLE); 37 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 38 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 39 | setContentView(R.layout.activity_live_room); 40 | 41 | Intent intent = getIntent(); 42 | String videoUrl = intent.getStringExtra("url"); 43 | if (TextUtils.isEmpty(videoUrl)) { 44 | return; 45 | } 46 | 47 | mVideoView = findViewById(R.id.video_view); 48 | mVideoView.setVideoPath(videoUrl); 49 | mVideoView.setLooping(false); 50 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FIT_PARENT); 51 | mVideoView.play(); 52 | 53 | findViewById(R.id.btn_01).setOnClickListener(new View.OnClickListener() { 54 | @Override 55 | public void onClick(View v) { 56 | if (scaleRatio == 1.0f) { 57 | scale(0.5f); 58 | } else { 59 | scale(1.0f); 60 | } 61 | } 62 | }); 63 | } 64 | 65 | @Override 66 | protected void onPause() { 67 | super.onPause(); 68 | if (mVideoView != null) { 69 | mVideoView.pause(); 70 | isPause = true; 71 | } 72 | } 73 | 74 | @Override 75 | protected void onResume() { 76 | super.onResume(); 77 | if (!isPause) { 78 | return; 79 | } 80 | 81 | if (mVideoView != null) { 82 | mVideoView.resume(); 83 | } 84 | } 85 | 86 | @Override 87 | protected void onDestroy() { 88 | if (mVideoView != null) { 89 | mVideoView.release(); 90 | } 91 | super.onDestroy(); 92 | } 93 | 94 | private void scale(float ratio) { 95 | if (scaleRatio == ratio) { 96 | return; 97 | } 98 | 99 | if (scaleAnimator != null) { 100 | scaleAnimator.cancel(); 101 | } 102 | 103 | PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scaleRatio, ratio); 104 | PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", scaleRatio, ratio); 105 | PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", 0); 106 | PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", 0); 107 | scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mVideoView, scaleX, scaleY, translationX, translationY); 108 | scaleAnimator.addListener(new AnimatorListenerAdapter() { 109 | @Override 110 | public void onAnimationEnd(Animator animation) { 111 | animation.removeAllListeners(); 112 | scaleAnimator = null; 113 | } 114 | }); 115 | scaleAnimator.setDuration(150L).start(); 116 | scaleRatio = ratio; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | 15 | findViewById(R.id.btn_01).setOnClickListener(new View.OnClickListener() { 16 | @Override 17 | public void onClick(View v) { 18 | startActivity(new Intent(MainActivity.this, LiveDemoActivity.class)); 19 | } 20 | }); 21 | findViewById(R.id.btn_02).setOnClickListener(new View.OnClickListener() { 22 | @Override 23 | public void onClick(View v) { 24 | startActivity(new Intent(MainActivity.this, LiveListActivity.class)); 25 | } 26 | }); 27 | 28 | findViewById(R.id.btn_03).setOnClickListener(new View.OnClickListener() { 29 | @Override 30 | public void onClick(View v) { 31 | String videoPath = "rtmp://live.hkstv.hk.lxdns.com/live/hks"; 32 | 33 | Intent intent = new Intent(MainActivity.this, LiveRoomActivity.class); 34 | intent.putExtra("url", videoPath); 35 | startActivity(intent); 36 | } 37 | }); 38 | 39 | findViewById(R.id.btn_06).setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | startActivity(new Intent(MainActivity.this, ScrollingActivity.class)); 43 | } 44 | }); 45 | 46 | // findViewById(R.id.btn_04).setOnClickListener(new View.OnClickListener() { 47 | // @Override 48 | // public void onClick(View v) { 49 | // startActivity(new Intent(MainActivity.this, XPlayerActivity.class)); 50 | // } 51 | // }); 52 | // 53 | // findViewById(R.id.btn_05).setOnClickListener(new View.OnClickListener() { 54 | // @Override 55 | // public void onClick(View v) { 56 | // 57 | // } 58 | // }); 59 | // 60 | // findViewById(R.id.btn_07).setOnClickListener(new View.OnClickListener() { 61 | // @Override 62 | // public void onClick(View v) { 63 | // 64 | // } 65 | // }); 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/ScrollingActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.AppBarLayout; 5 | import android.support.design.widget.CollapsingToolbarLayout; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | 10 | import com.anbetter.xplayer.ijk.XDragVideoView; 11 | import com.anbetter.xplayer.ijk.api.IXMediaPlayer; 12 | import com.anbetter.xplayer.ijk.api.IXRenderView; 13 | import com.anbetter.xplayer.ijk.api.IXVideoView; 14 | import com.anbetter.xplayer.ijk.delegate.XMediaPlayerDelegate; 15 | 16 | /** 17 | * 大小视频窗口切换Demo 18 | * 实现思路: 19 | * 1、采用多个XIVideoView共享同一个XIMediaPlayer播放器对象 20 | * 2、在滚动结束后,为XIMediaPlayer对象设置当前显示XIVideoView所持有的Surface 21 | * 3、XIMediaPlayer播放器播放视频的动作从未停止,只是切换展示视频画面的Surface而已,从而实现无缝切换大小窗口的功能 22 | * 4、小窗口支持在屏幕任意位置拖动,在拖动过程中播放动作不中断。 23 | *

24 | * Created by android_ls on 2018/4/26. 25 | * 26 | * @author android_ls 27 | * @version 1.0 28 | */ 29 | public class ScrollingActivity extends AppCompatActivity implements AppBarLayout.OnOffsetChangedListener { 30 | 31 | public static final int APP_BAR_LAYOUT_IDLE = 0; 32 | public static final int APP_BAR_LAYOUT_EXPANDED = 1; 33 | public static final int APP_BAR_LAYOUT_COLLAPSED = 2; 34 | 35 | private AppBarLayout mAppBarLayout; 36 | private CollapsingToolbarLayout mCollapsingToolbarLayout; 37 | 38 | private boolean isPause; 39 | private String videoUrl; 40 | private int mAppBarLayoutCurrentState = APP_BAR_LAYOUT_IDLE; 41 | 42 | private IXMediaPlayer mMediaPlayer; 43 | private IXVideoView mVideoView; 44 | private XDragVideoView mSmallVideoView; 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_scrolling); 50 | 51 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 52 | setSupportActionBar(toolbar); 53 | 54 | mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout); 55 | mCollapsingToolbarLayout.setTitle("XPlayer"); 56 | 57 | mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar); 58 | mAppBarLayout.addOnOffsetChangedListener(this); 59 | 60 | videoUrl = "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f30.mp4"; 61 | 62 | mSmallVideoView = findViewById(R.id.drag_video_view); 63 | mVideoView = findViewById(R.id.video_view); 64 | 65 | mMediaPlayer = XMediaPlayerDelegate.getInstance(); 66 | 67 | mSmallVideoView.setMediaPlayer(mMediaPlayer); 68 | mSmallVideoView.setDisplayAspectRatio(IXRenderView.AR_MATCH_PARENT); 69 | 70 | mVideoView.setMediaPlayer(mMediaPlayer); 71 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_MATCH_PARENT); 72 | 73 | mMediaPlayer.setVideoPath(videoUrl); 74 | mMediaPlayer.setLooping(false); 75 | mMediaPlayer.play(); 76 | } 77 | 78 | @Override 79 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { 80 | // MLog.i("verticalOffset = " + verticalOffset); 81 | if (verticalOffset == 0) { 82 | if (mAppBarLayoutCurrentState != APP_BAR_LAYOUT_EXPANDED) { 83 | mCollapsingToolbarLayout.setTitle(""); 84 | 85 | if (mVideoView.getSurface() != null) { 86 | mMediaPlayer.setSurface(mVideoView.getSurface()); 87 | } 88 | 89 | if (mSmallVideoView.getVisibility() == View.VISIBLE) { 90 | mSmallVideoView.setVisibility(View.GONE); 91 | } 92 | 93 | mAppBarLayoutCurrentState = APP_BAR_LAYOUT_EXPANDED; 94 | } 95 | } else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) { 96 | if (mAppBarLayoutCurrentState != APP_BAR_LAYOUT_COLLAPSED) { 97 | mCollapsingToolbarLayout.setTitle("大小视频显示组件互动"); 98 | 99 | if (mSmallVideoView.getVisibility() == View.GONE) { 100 | mSmallVideoView.setVisibility(View.VISIBLE); 101 | // 重置到初始化所在位置 102 | mSmallVideoView.restorePosition(); 103 | 104 | if (mSmallVideoView.getSurface() != null) { 105 | mMediaPlayer.setSurface(mSmallVideoView.getSurface()); 106 | } 107 | } 108 | 109 | mAppBarLayoutCurrentState = APP_BAR_LAYOUT_COLLAPSED; 110 | } 111 | } else { 112 | if (mAppBarLayoutCurrentState != APP_BAR_LAYOUT_IDLE) { 113 | if (mAppBarLayoutCurrentState == APP_BAR_LAYOUT_COLLAPSED) { 114 | // 中间状态 115 | mSmallVideoView.postDelayed(new Runnable() { 116 | @Override 117 | public void run() { 118 | if (mVideoView.getSurface() != null) { 119 | mMediaPlayer.setSurface(mVideoView.getSurface()); 120 | } 121 | 122 | if (mSmallVideoView.getVisibility() == View.VISIBLE) { 123 | mSmallVideoView.setVisibility(View.GONE); 124 | } 125 | } 126 | }, 50); 127 | } 128 | mAppBarLayoutCurrentState = APP_BAR_LAYOUT_IDLE; 129 | } 130 | } 131 | } 132 | 133 | @Override 134 | protected void onPause() { 135 | super.onPause(); 136 | if (mMediaPlayer != null) { 137 | mMediaPlayer.pause(); 138 | isPause = true; 139 | } 140 | } 141 | 142 | @Override 143 | protected void onResume() { 144 | super.onResume(); 145 | if (!isPause) { 146 | return; 147 | } 148 | 149 | if (mMediaPlayer != null) { 150 | mMediaPlayer.resume(); 151 | } 152 | } 153 | 154 | @Override 155 | protected void onDestroy() { 156 | if (mMediaPlayer != null) { 157 | mMediaPlayer.release(); 158 | } 159 | super.onDestroy(); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/VideoListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.content.Intent; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import com.anbetter.log.MLog; 11 | import com.anbetter.xplayer.ijk.api.IXRenderView; 12 | import com.anbetter.xplayer.ijk.api.IXVideoView; 13 | import com.anbetter.xplayer.ijk.delegate.XMediaPlayerDelegate; 14 | import com.anbetter.xplayer.model.VideoInfo; 15 | import com.facebook.drawee.view.SimpleDraweeView; 16 | import com.facebook.fresco.helper.Phoenix; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * 视频列表数据适配器 22 | * 23 | * @author android_ls 24 | * @version 1.0 25 | *

26 | * Created by android_ls on 2018/4/23. 27 | */ 28 | public class VideoListAdapter extends RecyclerView.Adapter { 29 | 30 | private ArrayList mList; 31 | 32 | public VideoListAdapter(ArrayList data) { 33 | mList = data; 34 | } 35 | 36 | @NonNull 37 | @Override 38 | public VideoPlayerHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 39 | return new VideoPlayerHolder(View.inflate(parent.getContext(), R.layout.live_list_item, null)); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(@NonNull VideoPlayerHolder holder, int position) { 44 | holder.bind(mList.get(position)); 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | return mList.size(); 50 | } 51 | 52 | @Override 53 | public void onViewRecycled(@NonNull VideoPlayerHolder holder) { 54 | MLog.i("---------onViewRecycled-----------"); 55 | holder.stop(); 56 | } 57 | 58 | public static class VideoPlayerHolder extends RecyclerView.ViewHolder { 59 | 60 | IXVideoView mVideoView; 61 | SimpleDraweeView sdvCoverView; 62 | TextView tvVideoDesc; 63 | 64 | public VideoPlayerHolder(View itemView) { 65 | super(itemView); 66 | mVideoView = itemView.findViewById(R.id.video_view); 67 | sdvCoverView = itemView.findViewById(R.id.sdv_cover_view); 68 | tvVideoDesc = itemView.findViewById(R.id.tv_video_desc); 69 | 70 | // 设置音视频播放器, 目的为了让列表共享同一个XIMediaPlayer对象 71 | mVideoView.setMediaPlayer(XMediaPlayerDelegate.getInstance()); 72 | } 73 | 74 | public void bind(final VideoInfo videoInfo) { 75 | mVideoView.setVideoPath(videoInfo.videoUrl); 76 | mVideoView.setLooping(false); 77 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_MATCH_PARENT); 78 | mVideoView.setCoverView(sdvCoverView); 79 | 80 | tvVideoDesc.setText("视频格式:" + videoInfo.desc); 81 | Phoenix.with(sdvCoverView).load(videoInfo.coverUrl); 82 | 83 | itemView.setOnClickListener(new View.OnClickListener() { 84 | 85 | @Override 86 | public void onClick(View v) { 87 | Intent intent = new Intent(itemView.getContext(), LiveRoomActivity.class); 88 | intent.putExtra("url", videoInfo.videoUrl); 89 | itemView.getContext().startActivity(intent); 90 | } 91 | }); 92 | } 93 | 94 | public void play() { 95 | mVideoView.play(); 96 | } 97 | 98 | public void stop() { 99 | mVideoView.stop(); 100 | } 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/XPlayerActivity.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.anbetter.xplayer.ijk.api.IXRenderView; 7 | import com.anbetter.xplayer.ijk.api.IXVideoView; 8 | 9 | /** 10 | * 视频播放器组件提供的基本功能示例Demo 11 | * 功能: 12 | * 1、支持播放直播流(rtmp) 13 | * 2、支持播放各种格式的多媒体(MP4、flv、MP3) 14 | * 3、支持多种视频画面显示方式:16:9、4:3、全屏、竖直居中等 15 | * 4、支持常用的动画(旋转、移动、缩放) 16 | * 5、支持倍速播放(加快或者减慢播放速度) 17 | *

18 | * Created by android_ls on 2018/4/23. 19 | * 20 | * @author android_ls 21 | * @version 1.0 22 | */ 23 | public class XPlayerActivity extends AppCompatActivity { 24 | 25 | private IXVideoView mVideoView; 26 | private int mRotation; 27 | private boolean isPause; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_player); 33 | 34 | // String videoPath = "rtmp://live.hkstv.hk.lxdns.com/live/hks"; 35 | String videoPath = "http://9890.vod.myqcloud.com/9890_4e292f9a3dd011e6b4078980237cc3d3.f30.mp4"; 36 | // String videoPath = "http://ips.ifeng.com/video19.ifeng.com/video09/2014/06/16/1989823-102-086-0009.mp4"; 37 | // String videoPath = "http://jzvd.nathen.cn/1b61da23555d4ce28c805ea303711aa5/7a33ac2af276441bb4b9838f32d8d710-5287d2089db37e62345123a1be272f8b.mp4"; 38 | 39 | 40 | mVideoView = findViewById(R.id.player_view); 41 | mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FIT_PARENT); 42 | mVideoView.setVideoPath(videoPath); 43 | mVideoView.play(); 44 | 45 | 46 | 47 | 48 | // findViewById(R.id.btn_play).setOnClickListener(new View.OnClickListener() { 49 | // @Override 50 | // public void onClick(View v) { 51 | // mVideoView.play(); 52 | // } 53 | // }); 54 | // findViewById(R.id.btn_pause).setOnClickListener(new View.OnClickListener() { 55 | // @Override 56 | // public void onClick(View v) { 57 | // mVideoView.pause(); 58 | // } 59 | // }); 60 | // 61 | // findViewById(R.id.btn_resume).setOnClickListener(new View.OnClickListener() { 62 | // @Override 63 | // public void onClick(View v) { 64 | // mVideoView.resume(); 65 | // } 66 | // }); 67 | // 68 | // findViewById(R.id.btn_01).setOnClickListener(new View.OnClickListener() { 69 | // @Override 70 | // public void onClick(View v) { 71 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FILL_PARENT); 72 | // } 73 | // }); 74 | // findViewById(R.id.btn_02).setOnClickListener(new View.OnClickListener() { 75 | // @Override 76 | // public void onClick(View v) { 77 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_FIT_PARENT); 78 | // } 79 | // }); 80 | // findViewById(R.id.btn_03).setOnClickListener(new View.OnClickListener() { 81 | // @Override 82 | // public void onClick(View v) { 83 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_ASPECT_WRAP_CONTENT); 84 | // } 85 | // }); 86 | // findViewById(R.id.btn_04).setOnClickListener(new View.OnClickListener() { 87 | // @Override 88 | // public void onClick(View v) { 89 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_4_3_FIT_PARENT); 90 | // } 91 | // }); 92 | // findViewById(R.id.btn_05).setOnClickListener(new View.OnClickListener() { 93 | // @Override 94 | // public void onClick(View v) { 95 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_16_9_FIT_PARENT); 96 | // } 97 | // }); 98 | // findViewById(R.id.btn_06).setOnClickListener(new View.OnClickListener() { 99 | // @Override 100 | // public void onClick(View v) { 101 | // mVideoView.setDisplayAspectRatio(IXRenderView.AR_MATCH_PARENT); 102 | // } 103 | // }); 104 | // 105 | // findViewById(R.id.btn_speed_01).setOnClickListener(new View.OnClickListener() { 106 | // @Override 107 | // public void onClick(View v) { 108 | // mVideoView.setSpeed(0.25f); 109 | // } 110 | // }); 111 | // findViewById(R.id.btn_speed_02).setOnClickListener(new View.OnClickListener() { 112 | // @Override 113 | // public void onClick(View v) { 114 | // mVideoView.setSpeed(0.5f); 115 | // } 116 | // }); 117 | // findViewById(R.id.btn_speed_03).setOnClickListener(new View.OnClickListener() { 118 | // @Override 119 | // public void onClick(View v) { 120 | // mVideoView.setSpeed(1.0f); 121 | // } 122 | // }); 123 | // findViewById(R.id.btn_speed_04).setOnClickListener(new View.OnClickListener() { 124 | // @Override 125 | // public void onClick(View v) { 126 | // mVideoView.setSpeed(1.5f); 127 | // } 128 | // }); 129 | // findViewById(R.id.btn_speed_05).setOnClickListener(new View.OnClickListener() { 130 | // @Override 131 | // public void onClick(View v) { 132 | // mVideoView.setSpeed(2.0f); 133 | // } 134 | // }); 135 | // 136 | // findViewById(R.id.btn_rotation_90).setOnClickListener(new View.OnClickListener() { 137 | // @Override 138 | // public void onClick(View v) { 139 | // mRotation += 90; 140 | // mVideoView.setVideoRotation(mRotation); 141 | // 142 | // if (mRotation == 360) { 143 | // mRotation = 0; 144 | // } 145 | // } 146 | // }); 147 | 148 | } 149 | 150 | @Override 151 | protected void onPause() { 152 | super.onPause(); 153 | if (mVideoView != null) { 154 | mVideoView.pause(); 155 | isPause = true; 156 | } 157 | } 158 | 159 | @Override 160 | protected void onResume() { 161 | super.onResume(); 162 | if (!isPause) { 163 | return; 164 | } 165 | 166 | if (mVideoView != null) { 167 | mVideoView.resume(); 168 | } 169 | } 170 | 171 | @Override 172 | protected void onDestroy() { 173 | if (mVideoView != null) { 174 | mVideoView.release(); 175 | } 176 | super.onDestroy(); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/DSVOrientation.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview; 2 | 3 | import android.graphics.Point; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by yarolegovich on 16.03.2017. 8 | */ 9 | public enum DSVOrientation { 10 | 11 | HORIZONTAL { 12 | @Override 13 | Helper createHelper() { 14 | return new HorizontalHelper(); 15 | } 16 | }, 17 | VERTICAL { 18 | @Override 19 | Helper createHelper() { 20 | return new VerticalHelper(); 21 | } 22 | }; 23 | 24 | //Package private 25 | abstract Helper createHelper(); 26 | 27 | interface Helper { 28 | 29 | int getViewEnd(int recyclerWidth, int recyclerHeight); 30 | 31 | int getDistanceToChangeCurrent(int childWidth, int childHeight); 32 | 33 | void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint); 34 | 35 | void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter); 36 | 37 | int getFlingVelocity(int velocityX, int velocityY); 38 | 39 | int getPendingDx(int pendingScroll); 40 | 41 | int getPendingDy(int pendingScroll); 42 | 43 | void offsetChildren(int amount, RecyclerViewProxy lm); 44 | 45 | float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY); 46 | 47 | boolean isViewVisible(Point center, int halfWidth, int halfHeight, int endBound, int extraSpace); 48 | 49 | boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm); 50 | 51 | boolean canScrollVertically(); 52 | 53 | boolean canScrollHorizontally(); 54 | } 55 | 56 | protected static class HorizontalHelper implements Helper { 57 | 58 | @Override 59 | public int getViewEnd(int recyclerWidth, int recyclerHeight) { 60 | return recyclerWidth; 61 | } 62 | 63 | @Override 64 | public int getDistanceToChangeCurrent(int childWidth, int childHeight) { 65 | return childWidth; 66 | } 67 | 68 | @Override 69 | public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) { 70 | int newX = recyclerCenter.x - scrolled; 71 | outPoint.set(newX, recyclerCenter.y); 72 | } 73 | 74 | @Override 75 | public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) { 76 | int newX = outCenter.x + direction.applyTo(shiftAmount); 77 | outCenter.set(newX, outCenter.y); 78 | } 79 | 80 | @Override 81 | public boolean isViewVisible( 82 | Point viewCenter, int halfWidth, int halfHeight, int endBound, 83 | int extraSpace) { 84 | int viewLeft = viewCenter.x - halfWidth; 85 | int viewRight = viewCenter.x + halfWidth; 86 | return viewLeft < (endBound + extraSpace) && viewRight > -extraSpace; 87 | } 88 | 89 | @Override 90 | public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) { 91 | View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild(); 92 | int leftBound = -lm.getExtraLayoutSpace(); 93 | int rightBound = lm.getWidth() + lm.getExtraLayoutSpace(); 94 | boolean isNewVisibleFromLeft = lm.getDecoratedLeft(firstChild) > leftBound 95 | && lm.getPosition(firstChild) > 0; 96 | boolean isNewVisibleFromRight = lm.getDecoratedRight(lastChild) < rightBound 97 | && lm.getPosition(lastChild) < lm.getItemCount() - 1; 98 | return isNewVisibleFromLeft || isNewVisibleFromRight; 99 | } 100 | 101 | @Override 102 | public void offsetChildren(int amount, RecyclerViewProxy helper) { 103 | helper.offsetChildrenHorizontal(amount); 104 | } 105 | 106 | @Override 107 | public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) { 108 | return viewCenterX - center.x; 109 | } 110 | 111 | @Override 112 | public int getFlingVelocity(int velocityX, int velocityY) { 113 | return velocityX; 114 | } 115 | 116 | @Override 117 | public boolean canScrollHorizontally() { 118 | return true; 119 | } 120 | 121 | @Override 122 | public boolean canScrollVertically() { 123 | return false; 124 | } 125 | 126 | @Override 127 | public int getPendingDx(int pendingScroll) { 128 | return pendingScroll; 129 | } 130 | 131 | @Override 132 | public int getPendingDy(int pendingScroll) { 133 | return 0; 134 | } 135 | } 136 | 137 | 138 | protected static class VerticalHelper implements Helper { 139 | 140 | @Override 141 | public int getViewEnd(int recyclerWidth, int recyclerHeight) { 142 | return recyclerHeight; 143 | } 144 | 145 | @Override 146 | public int getDistanceToChangeCurrent(int childWidth, int childHeight) { 147 | return childHeight; 148 | } 149 | 150 | @Override 151 | public void setCurrentViewCenter(Point recyclerCenter, int scrolled, Point outPoint) { 152 | int newY = recyclerCenter.y - scrolled; 153 | outPoint.set(recyclerCenter.x, newY); 154 | } 155 | 156 | @Override 157 | public void shiftViewCenter(Direction direction, int shiftAmount, Point outCenter) { 158 | int newY = outCenter.y + direction.applyTo(shiftAmount); 159 | outCenter.set(outCenter.x, newY); 160 | } 161 | 162 | @Override 163 | public void offsetChildren(int amount, RecyclerViewProxy helper) { 164 | helper.offsetChildrenVertical(amount); 165 | } 166 | 167 | @Override 168 | public float getDistanceFromCenter(Point center, int viewCenterX, int viewCenterY) { 169 | return viewCenterY - center.y; 170 | } 171 | 172 | @Override 173 | public boolean isViewVisible( 174 | Point viewCenter, int halfWidth, int halfHeight, int endBound, 175 | int extraSpace) { 176 | int viewTop = viewCenter.y - halfHeight; 177 | int viewBottom = viewCenter.y + halfHeight; 178 | return viewTop < (endBound + extraSpace) && viewBottom > -extraSpace; 179 | } 180 | 181 | @Override 182 | public boolean hasNewBecomeVisible(DiscreteScrollLayoutManager lm) { 183 | View firstChild = lm.getFirstChild(), lastChild = lm.getLastChild(); 184 | int topBound = -lm.getExtraLayoutSpace(); 185 | int bottomBound = lm.getHeight() + lm.getExtraLayoutSpace(); 186 | boolean isNewVisibleFromTop = lm.getDecoratedTop(firstChild) > topBound 187 | && lm.getPosition(firstChild) > 0; 188 | boolean isNewVisibleFromBottom = lm.getDecoratedBottom(lastChild) < bottomBound 189 | && lm.getPosition(lastChild) < lm.getItemCount() - 1; 190 | return isNewVisibleFromTop || isNewVisibleFromBottom; 191 | } 192 | 193 | @Override 194 | public int getFlingVelocity(int velocityX, int velocityY) { 195 | return velocityY; 196 | } 197 | 198 | @Override 199 | public boolean canScrollHorizontally() { 200 | return false; 201 | } 202 | 203 | @Override 204 | public boolean canScrollVertically() { 205 | return true; 206 | } 207 | 208 | @Override 209 | public int getPendingDx(int pendingScroll) { 210 | return 0; 211 | } 212 | 213 | @Override 214 | public int getPendingDy(int pendingScroll) { 215 | return pendingScroll; 216 | } 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/Direction.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview; 2 | 3 | /** 4 | * Created by yarolegovich on 16.03.2017. 5 | */ 6 | enum Direction { 7 | 8 | START { 9 | @Override 10 | public int applyTo(int delta) { 11 | return delta * -1; 12 | } 13 | 14 | @Override 15 | public boolean sameAs(int direction) { 16 | return direction < 0; 17 | } 18 | }, 19 | END { 20 | @Override 21 | public int applyTo(int delta) { 22 | return delta; 23 | } 24 | 25 | @Override 26 | public boolean sameAs(int direction) { 27 | return direction > 0; 28 | } 29 | }; 30 | 31 | public abstract int applyTo(int delta); 32 | 33 | public abstract boolean sameAs(int direction); 34 | 35 | public static Direction fromDelta(int delta) { 36 | return delta > 0 ? END : START; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/DiscreteScrollView.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.IntRange; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import com.anbetter.xplayer.discretescrollview.transform.DiscreteScrollItemTransformer; 12 | import com.anbetter.xplayer.discretescrollview.util.ScrollListenerAdapter; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by yarolegovich on 18.02.2017. 19 | */ 20 | @SuppressWarnings("unchecked") 21 | public class DiscreteScrollView extends RecyclerView { 22 | 23 | public static final int NO_POSITION = DiscreteScrollLayoutManager.NO_POSITION; 24 | 25 | private static final int DEFAULT_ORIENTATION = DSVOrientation.HORIZONTAL.ordinal(); 26 | 27 | private DiscreteScrollLayoutManager layoutManager; 28 | 29 | private List scrollStateChangeListeners; 30 | private List onItemChangedListeners; 31 | 32 | private boolean isOverScrollEnabled; 33 | int orientation = DEFAULT_ORIENTATION; 34 | 35 | public DiscreteScrollView(Context context) { 36 | super(context); 37 | init(null); 38 | } 39 | 40 | public DiscreteScrollView(Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | init(attrs); 43 | } 44 | 45 | public DiscreteScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | init(attrs); 48 | } 49 | 50 | private void init(AttributeSet attrs) { 51 | scrollStateChangeListeners = new ArrayList<>(); 52 | onItemChangedListeners = new ArrayList<>(); 53 | 54 | isOverScrollEnabled = getOverScrollMode() != OVER_SCROLL_NEVER; 55 | 56 | layoutManager = new DiscreteScrollLayoutManager( 57 | getContext(), new ScrollStateListener(), 58 | DSVOrientation.values()[orientation]); 59 | setLayoutManager(layoutManager); 60 | } 61 | 62 | @Override 63 | public void setLayoutManager(LayoutManager layout) { 64 | if (layout instanceof DiscreteScrollLayoutManager) { 65 | super.setLayoutManager(layout); 66 | } else { 67 | // throw new IllegalArgumentException(getContext().getString(R.string.dsv_ex_msg_dont_set_lm)); 68 | } 69 | } 70 | 71 | 72 | @Override 73 | public boolean fling(int velocityX, int velocityY) { 74 | boolean isFling = super.fling(velocityX, velocityY); 75 | if (isFling) { 76 | layoutManager.onFling(velocityX, velocityY); 77 | } else { 78 | layoutManager.returnToCurrentPosition(); 79 | } 80 | return isFling; 81 | } 82 | 83 | @Nullable 84 | public ViewHolder getViewHolder(int position) { 85 | View view = layoutManager.findViewByPosition(position); 86 | return view != null ? getChildViewHolder(view) : null; 87 | } 88 | 89 | /** 90 | * @return adapter position of the current item or -1 if nothing is selected 91 | */ 92 | public int getCurrentItem() { 93 | return layoutManager.getCurrentPosition(); 94 | } 95 | 96 | public void setItemTransformer(DiscreteScrollItemTransformer transformer) { 97 | layoutManager.setItemTransformer(transformer); 98 | } 99 | 100 | public void setItemTransitionTimeMillis(@IntRange(from = 10) int millis) { 101 | layoutManager.setTimeForItemSettle(millis); 102 | } 103 | 104 | public void setSlideOnFling(boolean result){ 105 | layoutManager.setShouldSlideOnFling(result); 106 | } 107 | 108 | public void setSlideOnFlingThreshold(int threshold){ 109 | layoutManager.setSlideOnFlingThreshold(threshold); 110 | } 111 | 112 | public void setOrientation(DSVOrientation orientation) { 113 | layoutManager.setOrientation(orientation); 114 | } 115 | 116 | public void setOffscreenItems(int items) { 117 | layoutManager.setOffscreenItems(items); 118 | } 119 | 120 | public void setClampTransformProgressAfter(@IntRange(from = 1) int itemCount) { 121 | if (itemCount <= 1) { 122 | throw new IllegalArgumentException("must be >= 1"); 123 | } 124 | layoutManager.setTransformClampItemCount(itemCount); 125 | } 126 | 127 | public void setOverScrollEnabled(boolean overScrollEnabled) { 128 | isOverScrollEnabled = overScrollEnabled; 129 | setOverScrollMode(OVER_SCROLL_NEVER); 130 | } 131 | 132 | public void addScrollStateChangeListener(@NonNull ScrollStateChangeListener scrollStateChangeListener) { 133 | scrollStateChangeListeners.add(scrollStateChangeListener); 134 | } 135 | 136 | public void addScrollListener(@NonNull ScrollListener scrollListener) { 137 | addScrollStateChangeListener(new ScrollListenerAdapter(scrollListener)); 138 | } 139 | 140 | public void addOnItemChangedListener(@NonNull OnItemChangedListener onItemChangedListener) { 141 | onItemChangedListeners.add(onItemChangedListener); 142 | } 143 | 144 | public void removeScrollStateChangeListener(@NonNull ScrollStateChangeListener scrollStateChangeListener) { 145 | scrollStateChangeListeners.remove(scrollStateChangeListener); 146 | } 147 | 148 | public void removeScrollListener(@NonNull ScrollListener scrollListener) { 149 | removeScrollStateChangeListener(new ScrollListenerAdapter<>(scrollListener)); 150 | } 151 | 152 | public void removeItemChangedListener(@NonNull OnItemChangedListener onItemChangedListener) { 153 | onItemChangedListeners.remove(onItemChangedListener); 154 | } 155 | 156 | private void notifyScrollStart(ViewHolder holder, int current) { 157 | for (ScrollStateChangeListener listener : scrollStateChangeListeners) { 158 | listener.onScrollStart(holder, current); 159 | } 160 | } 161 | 162 | private void notifyScrollEnd(ViewHolder holder, int current) { 163 | for (ScrollStateChangeListener listener : scrollStateChangeListeners) { 164 | listener.onScrollEnd(holder, current); 165 | } 166 | } 167 | 168 | private void notifyScroll(float position, 169 | int currentIndex, int newIndex, 170 | ViewHolder currentHolder, ViewHolder newHolder) { 171 | for (ScrollStateChangeListener listener : scrollStateChangeListeners) { 172 | listener.onScroll(position, currentIndex, newIndex, 173 | currentHolder, 174 | newHolder); 175 | } 176 | } 177 | 178 | private void notifyCurrentItemChanged(ViewHolder holder, int current) { 179 | for (OnItemChangedListener listener : onItemChangedListeners) { 180 | listener.onCurrentItemChanged(holder, current); 181 | } 182 | } 183 | 184 | private void notifyCurrentItemChanged() { 185 | if (onItemChangedListeners.isEmpty()) { 186 | return; 187 | } 188 | int current = layoutManager.getCurrentPosition(); 189 | ViewHolder currentHolder = getViewHolder(current); 190 | notifyCurrentItemChanged(currentHolder, current); 191 | } 192 | 193 | private class ScrollStateListener implements DiscreteScrollLayoutManager.ScrollStateListener { 194 | 195 | @Override 196 | public void onIsBoundReachedFlagChange(boolean isBoundReached) { 197 | if (isOverScrollEnabled) { 198 | setOverScrollMode(isBoundReached ? OVER_SCROLL_ALWAYS : OVER_SCROLL_NEVER); 199 | } 200 | } 201 | 202 | @Override 203 | public void onScrollStart() { 204 | if (scrollStateChangeListeners.isEmpty()) { 205 | return; 206 | } 207 | int current = layoutManager.getCurrentPosition(); 208 | ViewHolder holder = getViewHolder(current); 209 | if (holder != null) { 210 | notifyScrollStart(holder, current); 211 | } 212 | } 213 | 214 | @Override 215 | public void onScrollEnd() { 216 | if (onItemChangedListeners.isEmpty() && scrollStateChangeListeners.isEmpty()) { 217 | return; 218 | } 219 | int current = layoutManager.getCurrentPosition(); 220 | ViewHolder holder = getViewHolder(current); 221 | if (holder != null) { 222 | notifyScrollEnd(holder, current); 223 | notifyCurrentItemChanged(holder, current); 224 | } 225 | } 226 | 227 | @Override 228 | public void onScroll(float currentViewPosition) { 229 | if (scrollStateChangeListeners.isEmpty()) { 230 | return; 231 | } 232 | int currentIndex = getCurrentItem(); 233 | int newIndex = layoutManager.getNextPosition(); 234 | if (currentIndex != newIndex) { 235 | notifyScroll(currentViewPosition, 236 | currentIndex, newIndex, 237 | getViewHolder(currentIndex), 238 | getViewHolder(newIndex)); 239 | } 240 | } 241 | 242 | @Override 243 | public void onCurrentViewFirstLayout() { 244 | post(new Runnable() { 245 | @Override 246 | public void run() { 247 | notifyCurrentItemChanged(); 248 | } 249 | }); 250 | } 251 | 252 | @Override 253 | public void onDataSetChangeChangedPosition() { 254 | notifyCurrentItemChanged(); 255 | } 256 | } 257 | 258 | public interface ScrollStateChangeListener { 259 | 260 | void onScrollStart(@NonNull T currentItemHolder, int adapterPosition); 261 | 262 | void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition); 263 | 264 | void onScroll(float scrollPosition, 265 | int currentPosition, 266 | int newPosition, 267 | @Nullable T currentHolder, 268 | @Nullable T newCurrent); 269 | } 270 | 271 | public interface ScrollListener { 272 | 273 | void onScroll(float scrollPosition, 274 | int currentPosition, int newPosition, 275 | @Nullable T currentHolder, 276 | @Nullable T newCurrent); 277 | } 278 | 279 | public interface OnItemChangedListener { 280 | /* 281 | * This method will be also triggered when view appears on the screen for the first time. 282 | * If data set is empty, viewHolder will be null and adapterPosition will be NO_POSITION 283 | */ 284 | void onCurrentItemChanged(@Nullable T viewHolder, int adapterPosition); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/InfiniteScrollAdapter.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | import java.util.Locale; 8 | 9 | /** 10 | * Created by yarolegovich on 28-Apr-17. 11 | */ 12 | 13 | public class InfiniteScrollAdapter extends RecyclerView.Adapter { 14 | 15 | private static final int NOT_INITIALIZED = -1; 16 | private static final int RESET_BOUND = 100; 17 | 18 | public static InfiniteScrollAdapter wrap( 19 | @NonNull RecyclerView.Adapter adapter) { 20 | return new InfiniteScrollAdapter<>(adapter); 21 | } 22 | 23 | private RecyclerView.Adapter wrapped; 24 | private DiscreteScrollLayoutManager layoutManager; 25 | 26 | private int currentRangeStart; 27 | 28 | public InfiniteScrollAdapter(@NonNull RecyclerView.Adapter wrapped) { 29 | this.wrapped = wrapped; 30 | this.wrapped.registerAdapterDataObserver(new DataSetChangeDelegate()); 31 | } 32 | 33 | @Override 34 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { 35 | wrapped.onAttachedToRecyclerView(recyclerView); 36 | if (recyclerView instanceof DiscreteScrollView) { 37 | layoutManager = (DiscreteScrollLayoutManager) recyclerView.getLayoutManager(); 38 | currentRangeStart = NOT_INITIALIZED; 39 | } else { 40 | // String msg = recyclerView.getContext().getString(R.string.dsv_ex_msg_adapter_wrong_recycler); 41 | // throw new RuntimeException(msg); 42 | } 43 | } 44 | 45 | @Override 46 | public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 47 | wrapped.onDetachedFromRecyclerView(recyclerView); 48 | layoutManager = null; 49 | } 50 | 51 | @Override 52 | public T onCreateViewHolder(ViewGroup parent, int viewType) { 53 | if (currentRangeStart == NOT_INITIALIZED) { 54 | resetRange(0); 55 | } 56 | return wrapped.onCreateViewHolder(parent, viewType); 57 | } 58 | 59 | @Override 60 | public void onBindViewHolder(T holder, int position) { 61 | wrapped.onBindViewHolder(holder, mapPositionToReal(position)); 62 | } 63 | 64 | @Override 65 | public int getItemViewType(int position) { 66 | return wrapped.getItemViewType(mapPositionToReal(position)); 67 | } 68 | 69 | @Override 70 | public int getItemCount() { 71 | return wrapped.getItemCount() <= 1 ? wrapped.getItemCount() : Integer.MAX_VALUE; 72 | } 73 | 74 | public int getRealItemCount() { 75 | return wrapped.getItemCount(); 76 | } 77 | 78 | public int getRealCurrentPosition() { 79 | return getRealPosition(layoutManager.getCurrentPosition()); 80 | } 81 | 82 | public int getRealPosition(int position) { 83 | return mapPositionToReal(position); 84 | } 85 | 86 | public int getClosestPosition(int position) { 87 | if (position >= wrapped.getItemCount()) { 88 | throw new IndexOutOfBoundsException(String.format(Locale.US, 89 | "requested position is outside adapter's bounds: position=%d, size=%d", 90 | position, wrapped.getItemCount())); 91 | } 92 | int adapterTarget = currentRangeStart + position; 93 | int adapterCurrent = layoutManager.getCurrentPosition(); 94 | if (adapterTarget == adapterCurrent) { 95 | return adapterCurrent; 96 | } else if (adapterTarget < adapterCurrent) { 97 | int adapterTargetNextSet = currentRangeStart + wrapped.getItemCount() + position; 98 | return adapterCurrent - adapterTarget < adapterTargetNextSet - adapterCurrent ? 99 | adapterTarget : adapterTargetNextSet; 100 | } else { 101 | int adapterTargetPrevSet = currentRangeStart - wrapped.getItemCount() + position; 102 | return adapterCurrent - adapterTargetPrevSet < adapterTarget - adapterCurrent ? 103 | adapterTargetPrevSet : adapterTarget; 104 | } 105 | } 106 | 107 | private int mapPositionToReal(int position) { 108 | int newPosition = position - currentRangeStart; 109 | if (newPosition >= wrapped.getItemCount()) { 110 | currentRangeStart += wrapped.getItemCount(); 111 | if (Integer.MAX_VALUE - currentRangeStart <= RESET_BOUND) { 112 | resetRange(0); 113 | } 114 | return 0; 115 | } else if (newPosition < 0) { 116 | currentRangeStart -= wrapped.getItemCount(); 117 | if (currentRangeStart <= RESET_BOUND) { 118 | resetRange(wrapped.getItemCount() - 1); 119 | } 120 | return wrapped.getItemCount() - 1; 121 | } else { 122 | return newPosition; 123 | } 124 | } 125 | 126 | private void resetRange(int newPosition) { 127 | if (getItemCount() == 1) { 128 | currentRangeStart = 0; 129 | layoutManager.scrollToPosition(0); 130 | } else { 131 | currentRangeStart = Integer.MAX_VALUE / 2; 132 | layoutManager.scrollToPosition(currentRangeStart + newPosition); 133 | } 134 | } 135 | 136 | //TODO: handle proper data set change notifications 137 | private class DataSetChangeDelegate extends RecyclerView.AdapterDataObserver { 138 | 139 | @Override 140 | public void onChanged() { 141 | resetRange(0); 142 | notifyDataSetChanged(); 143 | } 144 | 145 | @Override 146 | public void onItemRangeRemoved(int positionStart, int itemCount) { 147 | onChanged(); 148 | } 149 | 150 | @Override 151 | public void onItemRangeChanged(int positionStart, int itemCount) { 152 | onChanged(); 153 | } 154 | 155 | @Override 156 | public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 157 | onChanged(); 158 | } 159 | 160 | @Override 161 | public void onItemRangeInserted(int positionStart, int itemCount) { 162 | onChanged(); 163 | } 164 | 165 | @Override 166 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 167 | onChanged(); 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/RecyclerViewProxy.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | /** 9 | * Created by yarolegovich on 10/25/17. 10 | */ 11 | public class RecyclerViewProxy { 12 | 13 | private RecyclerView.LayoutManager layoutManager; 14 | 15 | public RecyclerViewProxy(@NonNull RecyclerView.LayoutManager layoutManager) { 16 | this.layoutManager = layoutManager; 17 | } 18 | 19 | public void attachView(View view) { 20 | layoutManager.attachView(view); 21 | } 22 | 23 | public void detachView(View view) { 24 | layoutManager.detachView(view); 25 | } 26 | 27 | public void detachAndScrapView(View view, RecyclerView.Recycler recycler) { 28 | layoutManager.detachAndScrapView(view, recycler); 29 | } 30 | 31 | public void detachAndScrapAttachedViews(RecyclerView.Recycler recycler) { 32 | layoutManager.detachAndScrapAttachedViews(recycler); 33 | } 34 | 35 | public void recycleView(View view, RecyclerView.Recycler recycler) { 36 | recycler.recycleView(view); 37 | } 38 | 39 | public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) { 40 | layoutManager.removeAndRecycleAllViews(recycler); 41 | } 42 | 43 | public int getChildCount() { 44 | return layoutManager.getChildCount(); 45 | } 46 | 47 | public int getItemCount() { 48 | return layoutManager.getItemCount(); 49 | } 50 | 51 | public View getMeasuredChildForAdapterPosition(int position, RecyclerView.Recycler recycler) { 52 | View view = recycler.getViewForPosition(position); 53 | layoutManager.addView(view); 54 | layoutManager.measureChildWithMargins(view, 0, 0); 55 | return view; 56 | } 57 | 58 | public void layoutDecoratedWithMargins(View v, int left, int top, int right, int bottom) { 59 | layoutManager.layoutDecoratedWithMargins(v, left, top, right, bottom); 60 | } 61 | 62 | public View getChildAt(int index) { 63 | return layoutManager.getChildAt(index); 64 | } 65 | 66 | public int getPosition(View view) { 67 | return layoutManager.getPosition(view); 68 | } 69 | 70 | public int getMeasuredWidthWithMargin(View child) { 71 | ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); 72 | return layoutManager.getDecoratedMeasuredWidth(child) + lp.leftMargin + lp.rightMargin; 73 | } 74 | 75 | public int getMeasuredHeightWithMargin(View child) { 76 | ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); 77 | return layoutManager.getDecoratedMeasuredHeight(child) + lp.topMargin + lp.bottomMargin; 78 | } 79 | 80 | public int getWidth() { 81 | return layoutManager.getWidth(); 82 | } 83 | 84 | public int getHeight() { 85 | return layoutManager.getHeight(); 86 | } 87 | 88 | public void offsetChildrenHorizontal(int amount) { 89 | layoutManager.offsetChildrenHorizontal(amount); 90 | } 91 | 92 | public void offsetChildrenVertical(int amount) { 93 | layoutManager.offsetChildrenVertical(amount); 94 | } 95 | 96 | public void requestLayout() { 97 | layoutManager.requestLayout(); 98 | } 99 | 100 | public void startSmoothScroll(RecyclerView.SmoothScroller smoothScroller) { 101 | layoutManager.startSmoothScroll(smoothScroller); 102 | } 103 | 104 | public void removeAllViews() { 105 | layoutManager.removeAllViews(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/transform/DiscreteScrollItemTransformer.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview.transform; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by yarolegovich on 02.03.2017. 7 | */ 8 | 9 | public interface DiscreteScrollItemTransformer { 10 | void transformItem(View item, float position); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/transform/Pivot.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview.transform; 2 | 3 | import android.support.annotation.IntDef; 4 | import android.view.View; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | /** 10 | * Created by yarolegovich on 03.03.2017. 11 | */ 12 | 13 | public class Pivot { 14 | 15 | public static final int AXIS_X = 0; 16 | public static final int AXIS_Y = 1; 17 | 18 | private static final int PIVOT_CENTER = -1; 19 | private static final int PIVOT_MAX = -2; 20 | 21 | private int axis; 22 | private int pivotPoint; 23 | 24 | public Pivot(@Axis int axis, int pivotPoint) { 25 | this.axis = axis; 26 | this.pivotPoint = pivotPoint; 27 | } 28 | 29 | public void setOn(View view) { 30 | if (axis == AXIS_X) { 31 | switch (pivotPoint) { 32 | case PIVOT_CENTER: 33 | view.setPivotX(view.getWidth() * 0.5f); 34 | break; 35 | case PIVOT_MAX: 36 | view.setPivotX(view.getWidth()); 37 | break; 38 | default: 39 | view.setPivotX(pivotPoint); 40 | break; 41 | } 42 | return; 43 | } 44 | 45 | if (axis == AXIS_Y) { 46 | switch (pivotPoint) { 47 | case PIVOT_CENTER: 48 | view.setPivotY(view.getHeight() * 0.5f); 49 | break; 50 | case PIVOT_MAX: 51 | view.setPivotY(view.getHeight()); 52 | break; 53 | default: 54 | view.setPivotY(pivotPoint); 55 | break; 56 | } 57 | } 58 | } 59 | 60 | @Axis 61 | public int getAxis() { 62 | return axis; 63 | } 64 | 65 | public enum X { 66 | LEFT { 67 | @Override 68 | public Pivot create() { 69 | return new Pivot(AXIS_X, 0); 70 | } 71 | }, 72 | CENTER { 73 | @Override 74 | public Pivot create() { 75 | return new Pivot(AXIS_X, PIVOT_CENTER); 76 | } 77 | }, 78 | RIGHT { 79 | @Override 80 | public Pivot create() { 81 | return new Pivot(AXIS_X, PIVOT_MAX); 82 | } 83 | }; 84 | 85 | public abstract Pivot create(); 86 | } 87 | 88 | public enum Y { 89 | TOP { 90 | @Override 91 | public Pivot create() { 92 | return new Pivot(AXIS_Y, 0); 93 | } 94 | }, 95 | CENTER { 96 | @Override 97 | public Pivot create() { 98 | return new Pivot(AXIS_Y, PIVOT_CENTER); 99 | } 100 | }, 101 | BOTTOM { 102 | @Override 103 | public Pivot create() { 104 | return new Pivot(AXIS_Y, PIVOT_MAX); 105 | } 106 | }; 107 | 108 | public abstract Pivot create(); 109 | } 110 | 111 | @IntDef({AXIS_X, AXIS_Y}) 112 | @Retention(RetentionPolicy.SOURCE) 113 | public @interface Axis{ 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/transform/ScaleTransformer.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview.transform; 2 | 3 | import android.support.annotation.FloatRange; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by yarolegovich on 03.03.2017. 8 | */ 9 | public class ScaleTransformer implements DiscreteScrollItemTransformer { 10 | 11 | private Pivot pivotX; 12 | private Pivot pivotY; 13 | private float minScale; 14 | private float maxMinDiff; 15 | 16 | public ScaleTransformer() { 17 | pivotX = Pivot.X.CENTER.create(); 18 | pivotY = Pivot.Y.CENTER.create(); 19 | minScale = 0.8f; 20 | maxMinDiff = 0.2f; 21 | } 22 | 23 | @Override 24 | public void transformItem(View item, float position) { 25 | pivotX.setOn(item); 26 | pivotY.setOn(item); 27 | float closenessToCenter = 1f - Math.abs(position); 28 | float scale = minScale + maxMinDiff * closenessToCenter; 29 | item.setScaleX(scale); 30 | item.setScaleY(scale); 31 | } 32 | 33 | public static class Builder { 34 | 35 | private ScaleTransformer transformer; 36 | private float maxScale; 37 | 38 | public Builder() { 39 | transformer = new ScaleTransformer(); 40 | maxScale = 1f; 41 | } 42 | 43 | public Builder setMinScale(@FloatRange(from = 0.01) float scale) { 44 | transformer.minScale = scale; 45 | return this; 46 | } 47 | 48 | public Builder setMaxScale(@FloatRange(from = 0.01) float scale) { 49 | maxScale = scale; 50 | return this; 51 | } 52 | 53 | public Builder setPivotX(Pivot.X pivotX) { 54 | return setPivotX(pivotX.create()); 55 | } 56 | 57 | public Builder setPivotX(Pivot pivot) { 58 | assertAxis(pivot, Pivot.AXIS_X); 59 | transformer.pivotX = pivot; 60 | return this; 61 | } 62 | 63 | public Builder setPivotY(Pivot.Y pivotY) { 64 | return setPivotY(pivotY.create()); 65 | } 66 | 67 | public Builder setPivotY(Pivot pivot) { 68 | assertAxis(pivot, Pivot.AXIS_Y); 69 | transformer.pivotY = pivot; 70 | return this; 71 | } 72 | 73 | public ScaleTransformer build() { 74 | transformer.maxMinDiff = maxScale - transformer.minScale; 75 | return transformer; 76 | } 77 | 78 | private void assertAxis(Pivot pivot, @Pivot.Axis int axis) { 79 | if (pivot.getAxis() != axis) { 80 | throw new IllegalArgumentException("You passed a Pivot for wrong axis."); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/discretescrollview/util/ScrollListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.discretescrollview.util; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.RecyclerView; 6 | 7 | import com.anbetter.xplayer.discretescrollview.DiscreteScrollView; 8 | 9 | /** 10 | * Created by yarolegovich on 16.03.2017. 11 | */ 12 | public class ScrollListenerAdapter implements DiscreteScrollView.ScrollStateChangeListener { 13 | 14 | private DiscreteScrollView.ScrollListener adaptee; 15 | 16 | public ScrollListenerAdapter(@NonNull DiscreteScrollView.ScrollListener adaptee) { 17 | this.adaptee = adaptee; 18 | } 19 | 20 | @Override 21 | public void onScrollStart(@NonNull T currentItemHolder, int adapterPosition) { 22 | 23 | } 24 | 25 | @Override 26 | public void onScrollEnd(@NonNull T currentItemHolder, int adapterPosition) { 27 | 28 | } 29 | 30 | @Override 31 | public void onScroll(float scrollPosition, 32 | int currentIndex, int newIndex, 33 | @Nullable T currentHolder, @Nullable T newCurrentHolder) { 34 | adaptee.onScroll(scrollPosition, currentIndex, newIndex, currentHolder, newCurrentHolder); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (obj instanceof ScrollListenerAdapter) { 40 | return adaptee.equals(((ScrollListenerAdapter) obj).adaptee); 41 | } else { 42 | return super.equals(obj); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/anbetter/xplayer/model/VideoInfo.java: -------------------------------------------------------------------------------- 1 | package com.anbetter.xplayer.model; 2 | 3 | /** 4 | * 视频信息实体类 5 | *

6 | * Created by android_ls on 2018/4/25. 7 | * 8 | * @author android_ls 9 | * @version 1.0 10 | */ 11 | public class VideoInfo { 12 | 13 | public String videoUrl; 14 | public String coverUrl; 15 | public String desc; 16 | 17 | public VideoInfo() { 18 | } 19 | 20 | public VideoInfo(String videoUrl, String coverUrl) { 21 | this.videoUrl = videoUrl; 22 | this.coverUrl = coverUrl; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/default_play_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xhdpi/default_play_img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/default_service_video_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xhdpi/default_service_video_dot.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/default_service_video_play_xiao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xhdpi/default_service_video_play_xiao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/default_service_video_suspend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xhdpi/default_service_video_suspend.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/default_service_xuanzhuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xhdpi/default_service_xuanzhuan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_play_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xxhdpi/default_play_img.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_service_video_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xxhdpi/default_service_video_dot.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_service_video_play_xiao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xxhdpi/default_service_video_play_xiao.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_service_video_suspend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xxhdpi/default_service_video_suspend.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/default_service_xuanzhuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hpdx/XPlayer2/06c16fc1d72fe275a6726655b7d6b777c9d39104/app/src/main/res/drawable-xxhdpi/default_service_xuanzhuan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_seekbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_seekbar_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_do_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_live_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 18 | 19 | 23 | 24 | 30 | 31 |