├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── zhgeaits │ │ └── risv │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── zhgeaits │ │ ├── demo │ │ └── MainActivity.java │ │ └── risv │ │ ├── CustomHorizontalScrollView.java │ │ ├── DimenConverter.java │ │ ├── MultiPointBar.java │ │ ├── MultiRangebar.java │ │ ├── RangeImageSelectedView.java │ │ └── VideoImageSeekbar.java │ └── res │ ├── drawable │ ├── img_bar_bg.xml │ ├── img_bar_left.xml │ ├── img_bar_right.xml │ ├── left_arrow_selector.xml │ ├── multi_range_bg.xml │ └── right_arrow_selector.xml │ ├── layout │ ├── activity_main.xml │ └── layout_image_seekbar.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── left_arrow_normal.png │ ├── left_arrow_press.png │ ├── point_arrow.png │ ├── right_arrow_normal.png │ ├── right_arrow_press.png │ ├── test.jpg │ └── vertical_bar.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── rangview.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | */build 11 | -------------------------------------------------------------------------------- /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 | ## 效果 2 | 3 | ![alt demo](http://ww1.sinaimg.cn/large/96ea0fd8gw1f1pmu5cpgkg209s0h619m.gif "demo") 4 | 5 | ## 使用 6 | 7 | 这是一个简单的区间选择控件,从以前的项目抽取出来的,所以里面会有包含一些业务代码,不过由于控件简单,请随意修改使用。 8 | 9 | ## TODO 10 | 11 | 1.优化代码使之成为更加通用的模块 12 | 13 | ## 实现 14 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "org.zhgeaits.risv" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:23.1.1' 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/shenqu_ugc/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/org/zhgeaits/risv/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.zhgeaits.risv; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package org.zhgeaits.demo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | 8 | import org.zhgeaits.risv.R; 9 | import org.zhgeaits.risv.VideoImageSeekbar; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class MainActivity extends Activity { 15 | 16 | private VideoImageSeekbar seekbar; 17 | private Button setImage; 18 | private Button addWindow; 19 | private Button addFixedWindow; 20 | private Button addrange; 21 | private Button addpoint; 22 | private Button removeWindow; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_main); 28 | 29 | seekbar = (VideoImageSeekbar) findViewById(R.id.image_bar); 30 | setImage = (Button) findViewById(R.id.set_image); 31 | addWindow = (Button) findViewById(R.id.window); 32 | addFixedWindow = (Button) findViewById(R.id.fixed_window); 33 | addrange = (Button) findViewById(R.id.add_range_bar); 34 | addpoint = (Button) findViewById(R.id.add_range_point); 35 | removeWindow = (Button) findViewById(R.id.remove_window); 36 | 37 | setImage.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | List ids = new ArrayList<>(); 41 | for (int i = 0; i < 20; i++) { 42 | ids.add(R.mipmap.test); 43 | } 44 | seekbar.setProgress(0); 45 | seekbar.setImageListIds(ids); 46 | } 47 | }); 48 | 49 | addWindow.setOnClickListener(new View.OnClickListener() { 50 | @Override 51 | public void onClick(View v) { 52 | seekbar.addScaledRangeView(0, seekbar.getCurrentProgress(), 30); 53 | } 54 | }); 55 | 56 | removeWindow.setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | seekbar.removeRangeView(0); 60 | } 61 | }); 62 | 63 | addFixedWindow.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | seekbar.addFixedRangeView(0, seekbar.getCurrentProgress(), 30, 30); 67 | } 68 | }); 69 | 70 | addrange.setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | if(seekbar.isRangeViewVisible()) { 74 | seekbar.addMarkRange(0); 75 | } 76 | } 77 | }); 78 | 79 | addpoint.setOnClickListener(new View.OnClickListener() { 80 | @Override 81 | public void onClick(View v) { 82 | if(seekbar.isRangeViewVisible()) { 83 | seekbar.addMarkPoint(0); 84 | } 85 | } 86 | }); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/CustomHorizontalScrollView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | import android.os.Handler; 20 | import android.os.Message; 21 | import android.util.AttributeSet; 22 | import android.view.MotionEvent; 23 | import android.view.View; 24 | import android.widget.HorizontalScrollView; 25 | 26 | /** 27 | * Created by zhangge on 2015/9/25. 28 | */ 29 | public class CustomHorizontalScrollView extends HorizontalScrollView { 30 | 31 | private boolean mRangeMoving = false; 32 | private boolean mFromUser = false; 33 | private long mFirstTouch = 0; 34 | private int mValidPointerId; 35 | private int mBarLength;//可见长度 36 | private int mImageBarLen;//图片的长度 37 | private int mSeekWidth;//整条bar的长度 38 | private int mTouchEventId = 12345; 39 | private OnScrollListener mListener; 40 | private Handler mHandler = new Handler() { 41 | 42 | private int lastX; 43 | 44 | @Override 45 | public void handleMessage(Message msg) { 46 | super.handleMessage(msg); 47 | View scroller = (View) msg.obj; 48 | if (msg.what == mTouchEventId) { 49 | if (lastX == scroller.getScrollX()) { 50 | if (mListener != null && mFromUser == false) { 51 | mListener.onScrollStop(); 52 | } 53 | } else { 54 | mHandler.sendMessageDelayed(mHandler.obtainMessage(mTouchEventId, scroller), 5); 55 | lastX = scroller.getScrollX(); 56 | } 57 | } 58 | } 59 | }; 60 | 61 | public CustomHorizontalScrollView(Context context) { 62 | super(context); 63 | } 64 | 65 | public CustomHorizontalScrollView(Context context, AttributeSet attrs) { 66 | super(context, attrs); 67 | } 68 | 69 | public CustomHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { 70 | super(context, attrs, defStyleAttr); 71 | } 72 | 73 | public void setRangeMoving(boolean rangeMoving) { 74 | this.mRangeMoving = rangeMoving; 75 | } 76 | 77 | public void setOnScrollListener(OnScrollListener listener) { 78 | this.mListener = listener; 79 | } 80 | 81 | public void setBarLength(int len) { 82 | this.mBarLength = len; 83 | } 84 | 85 | public void setImageBarLen(int len) { 86 | this.mImageBarLen = len; 87 | } 88 | 89 | public void setSeekWidth(int len) { 90 | this.mSeekWidth = len; 91 | } 92 | 93 | @Override 94 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 95 | super.onScrollChanged(l, t, oldl, oldt); 96 | if (mListener != null) { 97 | mListener.onScrollChanged(l, t, oldl, oldt, mFromUser); 98 | } 99 | } 100 | 101 | @Override 102 | public boolean onTouchEvent(MotionEvent event) { 103 | switch (event.getAction()) { 104 | case MotionEvent.ACTION_DOWN: 105 | mFromUser = true; 106 | mFirstTouch = System.currentTimeMillis(); 107 | mValidPointerId = event.getPointerId(event.getActionIndex()); 108 | break; 109 | case MotionEvent.ACTION_MOVE: 110 | mFromUser = true; 111 | break; 112 | case MotionEvent.ACTION_UP: 113 | mFromUser = false; 114 | mHandler.sendMessageDelayed(mHandler.obtainMessage(mTouchEventId, this), 5); 115 | 116 | //如果300毫秒之间就是一个点击事件 117 | if (event.getPointerId(event.getActionIndex()) == mValidPointerId && System.currentTimeMillis() - mFirstTouch <= 300) { 118 | float posX = event.getX(); 119 | float blankWidth = (mSeekWidth - mImageBarLen) / 2.0f; 120 | if (posX + getScrollX() <= mSeekWidth - blankWidth && posX + getScrollX() >= blankWidth) { 121 | int distance = (int) (posX - (mBarLength / 2.0d)); 122 | if (mListener != null) { 123 | mListener.onScrollChanged(getScrollX() + distance, 0, getScrollX(), 0, true); 124 | } 125 | this.smoothScrollTo(getScrollX() + distance, 0); 126 | } 127 | } 128 | break; 129 | } 130 | return super.onTouchEvent(event); 131 | } 132 | 133 | @Override 134 | public boolean onInterceptTouchEvent(MotionEvent ev) { 135 | if (!mRangeMoving) { 136 | return super.onInterceptTouchEvent(ev); 137 | } else { 138 | return false; 139 | } 140 | } 141 | 142 | public interface OnScrollListener { 143 | void onScrollStop(); 144 | 145 | void onScrollChanged(int l, int t, int oldl, int oldt, boolean fromUser); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/DimenConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | 20 | public class DimenConverter { 21 | 22 | public static int dip2px(Context context, float dpValue) { 23 | final float scale = context.getResources().getDisplayMetrics().density; 24 | return (int) (dpValue * scale + 0.5f); 25 | } 26 | 27 | public static int px2dip(Context context, float pxValue) { 28 | final float scale = context.getResources().getDisplayMetrics().density; 29 | return (int) (pxValue / scale + 0.5f); 30 | } 31 | public static int px2sp(Context context, float pxValue) { 32 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 33 | return (int) (pxValue / fontScale + 0.5f); 34 | } 35 | 36 | public static int sp2px(Context context, float spValue) { 37 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 38 | return (int) (spValue * fontScale + 0.5f); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/MultiPointBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Paint; 21 | import android.util.AttributeSet; 22 | import android.view.View; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * Created by zhangge on 2015/10/12. 29 | */ 30 | public class MultiPointBar extends View { 31 | 32 | private Context mContext; 33 | private Paint mPaint; 34 | 35 | private Map mPoints; 36 | private int mBarHeight; 37 | private int mBarWidth; 38 | 39 | public MultiPointBar(Context context) { 40 | super(context); 41 | init(context, null, 0); 42 | } 43 | 44 | public MultiPointBar(Context context, AttributeSet attrs) { 45 | super(context, attrs); 46 | init(context, attrs, 0); 47 | } 48 | 49 | public MultiPointBar(Context context, AttributeSet attrs, int defStyleAttr) { 50 | super(context, attrs, defStyleAttr); 51 | init(context, attrs, defStyleAttr); 52 | } 53 | 54 | @Override 55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 56 | 57 | if (mBarWidth == 0) { 58 | mBarWidth = MeasureSpec.getSize(widthMeasureSpec); 59 | } 60 | 61 | int width = measureDimension(mBarWidth, widthMeasureSpec); 62 | int height = measureDimension(mBarHeight, heightMeasureSpec); 63 | 64 | setMeasuredDimension(width, height); 65 | } 66 | 67 | @Override 68 | protected void onDraw(Canvas canvas) { 69 | super.onDraw(canvas); 70 | 71 | for (Point point : mPoints.values()) { 72 | mPaint.setColor(point.color); 73 | canvas.drawCircle(point.position + mBarHeight / 2, mBarHeight / 2, mBarHeight / 2, mPaint); 74 | } 75 | } 76 | 77 | protected int measureDimension(int defaultSize, int measureSpec) { 78 | 79 | int result; 80 | 81 | int specMode = MeasureSpec.getMode(measureSpec); 82 | int specSize = MeasureSpec.getSize(measureSpec); 83 | 84 | if (specMode == MeasureSpec.EXACTLY) { 85 | result = specSize; 86 | } else if (specMode == MeasureSpec.AT_MOST) { 87 | result = Math.min(defaultSize, specSize); 88 | } else { 89 | result = defaultSize; 90 | } 91 | 92 | return result; 93 | } 94 | 95 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 96 | mContext = context; 97 | mPoints = new HashMap<>(); 98 | mBarHeight = DimenConverter.dip2px(mContext, 4); 99 | mPaint = new Paint(); 100 | mPaint.setAntiAlias(true); 101 | } 102 | 103 | public void addPoint(int id, int position, int color) { 104 | Point range = new Point(); 105 | range.position = position; 106 | range.color = color; 107 | mPoints.put(id, range); 108 | invalidate(); 109 | } 110 | 111 | public void removePoint(int id) { 112 | mPoints.remove(id); 113 | invalidate(); 114 | } 115 | 116 | /** 117 | * 设置bar的长度 118 | * @param barWidth 119 | */ 120 | public void setBarWidth(int barWidth) { 121 | this.mBarWidth = barWidth; 122 | } 123 | 124 | public class Point { 125 | int position; 126 | int color; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/MultiRangebar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.drawable.Drawable; 21 | import android.util.AttributeSet; 22 | import android.view.View; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * Created by zhangge on 15/9/27. 29 | * 30 | */ 31 | public class MultiRangebar extends View { 32 | 33 | private Context mContext; 34 | private Drawable mSelectedBar; 35 | 36 | private Map mRanges; 37 | private int mBarHeight; 38 | private int mBarWidth; 39 | 40 | public MultiRangebar(Context context) { 41 | super(context); 42 | init(context, null, 0); 43 | } 44 | 45 | public MultiRangebar(Context context, AttributeSet attrs) { 46 | super(context, attrs); 47 | init(context, attrs, 0); 48 | } 49 | 50 | public MultiRangebar(Context context, AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | init(context, attrs, defStyleAttr); 53 | } 54 | 55 | @Override 56 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 57 | 58 | if (mBarWidth == 0) { 59 | mBarWidth = MeasureSpec.getSize(widthMeasureSpec); 60 | } 61 | 62 | int width = measureDimension(mBarWidth, widthMeasureSpec); 63 | int height = measureDimension(mBarHeight, heightMeasureSpec); 64 | 65 | setMeasuredDimension(width, height); 66 | } 67 | 68 | @Override 69 | protected void onDraw(Canvas canvas) { 70 | super.onDraw(canvas); 71 | 72 | for (Range range : mRanges.values()) { 73 | mSelectedBar.setBounds(range.left, 0, range.right, mBarHeight); 74 | mSelectedBar.draw(canvas); 75 | } 76 | } 77 | 78 | protected int measureDimension(int defaultSize, int measureSpec) { 79 | 80 | int result; 81 | 82 | int specMode = MeasureSpec.getMode(measureSpec); 83 | int specSize = MeasureSpec.getSize(measureSpec); 84 | 85 | if (specMode == MeasureSpec.EXACTLY) { 86 | result = specSize; 87 | } else if (specMode == MeasureSpec.AT_MOST) { 88 | result = Math.min(defaultSize, specSize); 89 | } else { 90 | result = defaultSize; 91 | } 92 | 93 | return result; 94 | } 95 | 96 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 97 | mContext = context; 98 | mRanges = new HashMap<>(); 99 | mBarHeight = DimenConverter.dip2px(mContext, 2); 100 | mSelectedBar = getResources().getDrawable(R.drawable.multi_range_bg); 101 | } 102 | 103 | public void addRange(int id, int left, int right) { 104 | Range range = new Range(); 105 | range.left = left; 106 | range.right = right; 107 | mRanges.put(id, range); 108 | invalidate(); 109 | } 110 | 111 | public void removeRange(int id) { 112 | mRanges.remove(id); 113 | invalidate(); 114 | } 115 | 116 | public void setBarWidth(int barWidth) { 117 | this.mBarWidth = barWidth; 118 | } 119 | 120 | public class Range { 121 | int left; 122 | int right; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/RangeImageSelectedView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | import android.graphics.Canvas; 20 | import android.graphics.Color; 21 | import android.graphics.Paint; 22 | import android.graphics.RectF; 23 | import android.graphics.drawable.Drawable; 24 | import android.util.AttributeSet; 25 | import android.view.MotionEvent; 26 | import android.view.View; 27 | 28 | /** 29 | * Created by zhangge on 2015/9/24. 30 | */ 31 | public class RangeImageSelectedView extends View { 32 | 33 | public static final int HINT_NONE = 1;//无效点击状态 34 | public static final int HINT_DIRECTION_LEFT = 2;//点击左边拖动 35 | public static final int HINT_DIRECTION_RIGHT = 3;//点击右边拖动状态 36 | public static final int CHANGE_TYPE_SCALE = 1;//区间修改的类型,修改区间的大小 37 | public static final int CHANGE_TYPE_MOVE = 2;//区间修改的类型,移动区间 38 | 39 | private Drawable mLeftSelectBgDrawable; 40 | private Drawable mLeftSelectArrowDrawable; 41 | private Drawable mRightSelectBgDrawable; 42 | private Drawable mRightSelectArrowDrawable; 43 | private Drawable mPointArrowDrawable; 44 | 45 | private Context mContext; 46 | private CustomHorizontalScrollView mHorizontalScrollView; 47 | private Paint mPaint; 48 | private Paint mOutsidePaint; 49 | 50 | private int id; 51 | private int mSeekWidth;//整条bar的宽度,包括前后空白 52 | private int mSelectWidth;//点击按钮的宽度 53 | private int mSelectHeight;//点击按钮的高度,就是整条bar的高度了 54 | private int mArrowWidth;//箭头的宽度 55 | private int mArrowHeight;//箭头的高度 56 | private int mPointArrowWidth;//3个点的宽度 57 | private int mPointArrowHeight;//3个点的高度 58 | private int mStartLeft;//画左边按钮的的起始位置 59 | private int mWindowLen;//窗口的宽度 60 | private int mBorder;//窗口的边框宽度 61 | private int mHitState;//点击的状态 62 | private int mValidPointerId;//有效的多点触控点Id 63 | private int mLeftBlankWidth;//左边空白的宽度 64 | private int mRightBlankWidth;//右边空白的宽度 65 | private int mImageListLen;//图片的长度,就是bar的宽度,不包括前后空白 66 | private int mSeekbarVisibleWidth;//bar的可见宽度 67 | private int mMinWindowLen = 0;//窗口的最小宽度,默认是0 68 | private int mMaxWindowLen = 0;//窗口的最大宽度,弹幕初始值最大的宽度 69 | private float mLastX;//上次点击的X坐标 70 | private float mLastY;//上次点击的Y坐标 71 | private boolean mCanChange = true;//是否可以修改 72 | private int mChangeType = 1;//区间修改的类型 73 | private boolean mLeftChange = false;//弹幕框移动到最右边以后开始变小 74 | 75 | private OnRangeChangedListener mRangeChangedListener; 76 | 77 | public RangeImageSelectedView(Context context) { 78 | super(context); 79 | init(context, null, 0); 80 | } 81 | 82 | public RangeImageSelectedView(Context context, AttributeSet attrs) { 83 | super(context, attrs); 84 | init(context, attrs, 0); 85 | } 86 | 87 | public RangeImageSelectedView(Context context, AttributeSet attrs, int defStyleAttr) { 88 | super(context, attrs, defStyleAttr); 89 | init(context, attrs, defStyleAttr); 90 | } 91 | 92 | private void init(Context context, AttributeSet attrs, int defStyleAttr) { 93 | mContext = context; 94 | mSelectWidth = DimenConverter.dip2px(context, 16); 95 | mSelectHeight = DimenConverter.dip2px(context, 44); 96 | mArrowWidth = DimenConverter.dip2px(context, 7); 97 | mArrowHeight = DimenConverter.dip2px(context, 15); 98 | mPointArrowWidth = DimenConverter.dip2px(context, 4); 99 | mPointArrowHeight = DimenConverter.dip2px(context, 19); 100 | mBorder = DimenConverter.dip2px(context, 4); 101 | mStartLeft = 0; 102 | mWindowLen = 10; 103 | 104 | mPaint = new Paint(); 105 | mPaint.setColor(Color.parseColor("#ff8900")); 106 | mPaint.setAntiAlias(true); 107 | 108 | mOutsidePaint = new Paint(); 109 | mOutsidePaint.setColor(Color.parseColor("#8A000000")); 110 | mOutsidePaint.setAntiAlias(true); 111 | 112 | mLeftSelectBgDrawable = getContext().getResources().getDrawable(R.drawable.img_bar_left); 113 | mLeftSelectArrowDrawable = getResources().getDrawable(R.drawable.left_arrow_selector); 114 | 115 | mRightSelectBgDrawable = getResources().getDrawable(R.drawable.img_bar_right); 116 | mRightSelectArrowDrawable = getResources().getDrawable(R.drawable.right_arrow_selector); 117 | 118 | mPointArrowDrawable = getResources().getDrawable(R.mipmap.point_arrow); 119 | } 120 | 121 | public void setOnRangeChangedListener(OnRangeChangedListener listener) { 122 | this.mRangeChangedListener = listener; 123 | } 124 | 125 | public int getId() { 126 | return id; 127 | } 128 | 129 | public void setId(int id) { 130 | this.id = id; 131 | } 132 | 133 | /** 134 | * 设置最小的窗口宽度 135 | * @param minWindowLen 136 | */ 137 | public void setMinWindowLen(int minWindowLen) { 138 | this.mMinWindowLen = minWindowLen; 139 | } 140 | 141 | /** 142 | * 设置最大的窗口宽度 143 | * @param maxWindowlen 144 | */ 145 | public void setMaxWindowLen(int maxWindowlen) { 146 | this.mMaxWindowLen = maxWindowlen; 147 | } 148 | 149 | /** 150 | * 获取当前的最大窗口值 151 | * @return 152 | */ 153 | public int getMaxWindowLen() { 154 | return this.mMaxWindowLen; 155 | } 156 | 157 | /** 158 | * 设置左边绘制的起点位置 159 | * @param startLeft 160 | */ 161 | public void setStartLeft(int startLeft) { 162 | this.mStartLeft = startLeft; 163 | } 164 | 165 | /** 166 | * 设置左边选择的按钮样式 167 | * @param leftChange 168 | */ 169 | public void setLeftChange(boolean leftChange) { 170 | mLeftChange = leftChange; 171 | invalidate(); 172 | } 173 | 174 | /** 175 | * 设置窗口的起始位置 176 | * @param windowStart 177 | */ 178 | public void setWindowStart(int windowStart) { 179 | this.mStartLeft = windowStart - mSelectWidth; 180 | } 181 | 182 | /** 183 | * 设置选择窗口的长度 184 | * @param windowLen 185 | */ 186 | public void setWindowLen(int windowLen) { 187 | this.mWindowLen = windowLen; 188 | } 189 | 190 | /** 191 | * 获取窗口起始位置 192 | * @return 193 | */ 194 | public int getWindowStart() { 195 | return mStartLeft + mSelectWidth; 196 | } 197 | 198 | /** 199 | * 获取窗口长度 200 | * @return 201 | */ 202 | public int getWindowLen() { 203 | return mWindowLen; 204 | } 205 | 206 | /** 207 | * 设置左边的空白长度 208 | * @param blankWidth 209 | */ 210 | public void setLeftBlankWidth(int blankWidth) { 211 | this.mLeftBlankWidth = blankWidth; 212 | } 213 | 214 | /** 215 | * 设置右边的空白宽度 216 | * @param blankWidth 217 | */ 218 | public void setRightBlankWidth(int blankWidth) { 219 | this.mRightBlankWidth = blankWidth; 220 | } 221 | 222 | /** 223 | * 设置可用bar的长度 224 | * @param imageLen 225 | */ 226 | public void setImageListLen(int imageLen) { 227 | this.mImageListLen = imageLen; 228 | } 229 | 230 | public void setHorizontalScrollView(CustomHorizontalScrollView scrollView) { 231 | this.mHorizontalScrollView = scrollView; 232 | } 233 | 234 | /** 235 | * 设置整个bar的宽度 236 | * @param width 237 | */ 238 | public void setSeekWidth(int width) { 239 | this.mSeekWidth = width; 240 | } 241 | 242 | /** 243 | * 设置bar的可见宽度 244 | * @param width 245 | */ 246 | public void setSeekbarVisiblewidth(int width) { 247 | this.mSeekbarVisibleWidth = width; 248 | } 249 | 250 | /** 251 | * 画外面的蒙层 252 | * 253 | * @param canvas 254 | */ 255 | private void drawOutsideLayer(Canvas canvas) { 256 | int radius = DimenConverter.dip2px(mContext, 3); 257 | RectF rectfl = new RectF(0, 0, mStartLeft + mSelectWidth / 2, mSelectHeight); 258 | RectF rectfr = new RectF(mStartLeft + mWindowLen + mSelectWidth + mSelectWidth / 2, 0, canvas.getWidth(), mSelectHeight); 259 | canvas.drawRoundRect(rectfl, radius, radius, mOutsidePaint); 260 | canvas.drawRoundRect(rectfr, radius, radius, mOutsidePaint); 261 | } 262 | 263 | /** 264 | * 画左边的3个点选择框 265 | * @param canvas 266 | */ 267 | private void drawLeftPointSelect(Canvas canvas) { 268 | mLeftSelectBgDrawable.setBounds(mStartLeft, 0, mStartLeft + mSelectWidth, mSelectHeight); 269 | mLeftSelectBgDrawable.draw(canvas); 270 | mPointArrowDrawable.setBounds((mSelectWidth - mPointArrowWidth) / 2 + mStartLeft, (mSelectHeight - mPointArrowHeight) / 2, 271 | (mSelectWidth - mPointArrowWidth) / 2 + mPointArrowWidth + mStartLeft, (mSelectHeight - mPointArrowHeight) / 2 + mPointArrowHeight); 272 | mPointArrowDrawable.draw(canvas); 273 | } 274 | 275 | /** 276 | * 画右边的3个点选择框 277 | * @param canvas 278 | */ 279 | private void drawRightPointSelect(Canvas canvas) { 280 | mRightSelectBgDrawable.setBounds(mStartLeft + mSelectWidth + mWindowLen, 0, mStartLeft + mSelectWidth + mWindowLen + mSelectWidth, mSelectHeight); 281 | mRightSelectBgDrawable.draw(canvas); 282 | mPointArrowDrawable.setBounds((mSelectWidth - mPointArrowWidth) / 2 + mStartLeft + mSelectWidth + mWindowLen, (mSelectHeight - mPointArrowHeight) / 2, 283 | (mSelectWidth - mPointArrowWidth) / 2 + mStartLeft + mSelectWidth + mWindowLen + mPointArrowWidth, (mSelectHeight - mPointArrowHeight) / 2 + mPointArrowHeight); 284 | mPointArrowDrawable.draw(canvas); 285 | } 286 | 287 | /** 288 | * 画左边拖动按钮 289 | * 290 | * @param canvas 291 | */ 292 | private void drawLeftSelect(Canvas canvas) { 293 | mLeftSelectBgDrawable.setBounds(mStartLeft, 0, mStartLeft + mSelectWidth, mSelectHeight); 294 | mLeftSelectBgDrawable.draw(canvas); 295 | mLeftSelectArrowDrawable.setBounds((mSelectWidth - mArrowWidth) / 2 + mStartLeft, (mSelectHeight - mArrowHeight) / 2, 296 | (mSelectWidth - mArrowWidth) / 2 + mArrowWidth + mStartLeft, (mSelectHeight - mArrowHeight) / 2 + mArrowHeight); 297 | mLeftSelectArrowDrawable.draw(canvas); 298 | } 299 | 300 | /** 301 | * 画中间的两条边框 302 | * 303 | * @param canvas 304 | */ 305 | private void drawCenterRect(Canvas canvas) { 306 | canvas.drawRect(mStartLeft + mSelectWidth / 2, 0, mStartLeft + mSelectWidth + mWindowLen + mSelectWidth / 2, mBorder, mPaint); 307 | canvas.drawRect(mStartLeft + mSelectWidth / 2, mSelectHeight - mBorder, mStartLeft + mSelectWidth + mWindowLen + mSelectWidth / 2, mSelectHeight, mPaint); 308 | } 309 | 310 | /** 311 | * 画右边的拖动按钮 312 | * 313 | * @param canvas 314 | */ 315 | private void drawRightSelect(Canvas canvas) { 316 | mRightSelectBgDrawable.setBounds(mStartLeft + mSelectWidth + mWindowLen, 0, mStartLeft + mSelectWidth + mWindowLen + mSelectWidth, mSelectHeight); 317 | mRightSelectBgDrawable.draw(canvas); 318 | mRightSelectArrowDrawable.setBounds((mSelectWidth - mArrowWidth) / 2 + mStartLeft + mSelectWidth + mWindowLen, (mSelectHeight - mArrowHeight) / 2, 319 | (mSelectWidth - mArrowWidth) / 2 + mStartLeft + mSelectWidth + mWindowLen + mArrowWidth, (mSelectHeight - mArrowHeight) / 2 + mArrowHeight); 320 | mRightSelectArrowDrawable.draw(canvas); 321 | } 322 | 323 | /** 324 | * 设置区间是否可以修改 325 | * @param canChange 326 | */ 327 | public void setCanChange(boolean canChange) { 328 | this.mCanChange = canChange; 329 | } 330 | 331 | /** 332 | * 设置区间的修改类型 333 | * @param type 334 | */ 335 | public void setChangeType(int type) { 336 | this.mChangeType = type; 337 | } 338 | 339 | /** 340 | * 获取区间的类型 341 | * @return 342 | */ 343 | public int getChangeType() { 344 | return this.mChangeType; 345 | } 346 | 347 | /** 348 | * 判断点击的位置 349 | * 350 | * @param x 351 | * @param y 352 | * @return 353 | */ 354 | private int getHit(float x, float y) { 355 | 356 | if (x >= mStartLeft && x <= mStartLeft + mSelectWidth) { 357 | return HINT_DIRECTION_LEFT; 358 | } 359 | if (x >= mStartLeft + mSelectWidth + mWindowLen && x <= mStartLeft + mSelectWidth + mWindowLen + mSelectWidth) { 360 | return HINT_DIRECTION_RIGHT; 361 | } 362 | 363 | return HINT_NONE; 364 | } 365 | 366 | /** 367 | * 移动窗口的大小 368 | * @param hintState 369 | * @param distance 370 | */ 371 | private void handleMotion(int hintState, int distance) { 372 | 373 | if (hintState == HINT_DIRECTION_LEFT) { 374 | int startLeft = mStartLeft + distance; 375 | int windowLen = mWindowLen - distance; 376 | if(windowLen >= mMinWindowLen && startLeft + mSelectWidth >= mLeftBlankWidth && startLeft >= mHorizontalScrollView.getScrollX()) { 377 | mWindowLen = windowLen; 378 | mStartLeft = startLeft; 379 | invalidate(); 380 | if(mRangeChangedListener != null) { 381 | int leftSelectPostion = mStartLeft + mSelectWidth; 382 | mRangeChangedListener.onRangeChanging(HINT_DIRECTION_LEFT, leftSelectPostion); 383 | } 384 | } 385 | } else if (hintState == HINT_DIRECTION_RIGHT) { 386 | int windowLen = mWindowLen + distance; 387 | int rightStart = mStartLeft + mSelectWidth + windowLen; 388 | //如果窗口宽度大于mMinWindowLen,或者没有超出右边范围就移动 389 | if (windowLen >= mMinWindowLen && rightStart <= mSeekWidth - mRightBlankWidth && (rightStart + mSelectWidth) <= (mHorizontalScrollView.getScrollX() + mSeekbarVisibleWidth)) { 390 | mWindowLen = windowLen; 391 | invalidate(); 392 | if(mRangeChangedListener != null) { 393 | mRangeChangedListener.onRangeChanging(HINT_DIRECTION_RIGHT, rightStart); 394 | } 395 | } 396 | } 397 | } 398 | 399 | /** 400 | * 移动区间 401 | * @param hintState 402 | * @param distance 403 | */ 404 | private void handleMoveMotion(int hintState, int distance) { 405 | int startLeft = mStartLeft + distance; 406 | int rightStart = startLeft + mSelectWidth + mWindowLen; 407 | 408 | if (hintState == HINT_DIRECTION_LEFT) { 409 | 410 | //如果触控点超出可视范围或者进度条最左端就不让移动了 411 | if(startLeft + mSelectWidth < mLeftBlankWidth || startLeft < mHorizontalScrollView.getScrollX()) { 412 | mLeftChange = false; 413 | invalidate(); 414 | return; 415 | } 416 | 417 | //向左拖动 418 | if(distance < 0) { 419 | //如果窗口没缩小的情况,则整体拖动 420 | if(mWindowLen == mMaxWindowLen) { 421 | mLeftChange = false; 422 | mStartLeft = startLeft; 423 | } else {//窗口被缩小的情况,则放大窗口 424 | mLeftChange = true; 425 | int windowLen = mWindowLen - distance; 426 | mWindowLen = windowLen; 427 | if(mWindowLen > mMaxWindowLen) { 428 | mWindowLen = mMaxWindowLen; 429 | } 430 | mStartLeft = startLeft; 431 | } 432 | } else if(distance > 0) {//向右拖动 433 | //如果已经顶到最右端了,并且窗口为0了,则不让拖动了 434 | if(mWindowLen <= 0 && rightStart >= mSeekWidth - mRightBlankWidth) { 435 | return; 436 | } 437 | 438 | //如果已经顶到最右端了则缩小窗口,否则整体移动 439 | //只能右边移动,不能缩小窗口 440 | if(rightStart >= mSeekWidth - mRightBlankWidth) { 441 | return; 442 | /*int windowLen = mWindowLen - distance; 443 | mWindowLen = windowLen; 444 | if(mWindowLen > mMaxWindowLen) { 445 | mWindowLen = mMaxWindowLen; 446 | } else if(mWindowLen < 0) { 447 | mWindowLen = 0; 448 | } 449 | mLeftChange = true; 450 | mStartLeft = startLeft;*/ 451 | } else { 452 | mLeftChange = false; 453 | mStartLeft = startLeft; 454 | } 455 | } 456 | 457 | invalidate(); 458 | 459 | if(mRangeChangedListener != null) { 460 | int leftSelectPostion = mStartLeft + mSelectWidth; 461 | mRangeChangedListener.onRangeChanging(HINT_DIRECTION_LEFT, leftSelectPostion); 462 | } 463 | 464 | } else if (hintState == HINT_DIRECTION_RIGHT) { 465 | 466 | //现在右边只有完整显示的情况下可以拖动,其他时候无法点击 467 | if(mWindowLen < mMaxWindowLen) { 468 | return; 469 | } 470 | 471 | //如果触控点超出可视范围或者进度条最左端就不让移动了 472 | if(rightStart > mSeekWidth - mRightBlankWidth || (rightStart + mSelectWidth) > (mHorizontalScrollView.getScrollX() + mSeekbarVisibleWidth)) { 473 | return; 474 | } 475 | 476 | //向右拖动 477 | if(distance > 0) { 478 | mLeftChange = false; 479 | //如果窗口没缩小的情况,则整体拖动 480 | if(mWindowLen == mMaxWindowLen) { 481 | mStartLeft = startLeft; 482 | } else {//窗口被缩小的情况,则放大窗口 483 | // mLeftChange = true; 484 | // int windowLen = mWindowLen + distance; 485 | // mWindowLen = windowLen; 486 | // if(mWindowLen > mMaxWindowLen) { 487 | // mWindowLen = mMaxWindowLen; 488 | // } 489 | return; 490 | } 491 | } else if(distance < 0) {//向左拖动 492 | mLeftChange = false; 493 | //如果已经顶到最左端了,并且窗口为0了,则不让拖动了 494 | if(mWindowLen <= 0 && startLeft + mSelectWidth <= mLeftBlankWidth) { 495 | return; 496 | } 497 | 498 | //如果已经顶到最左端了则缩小窗口,否则整体移动 499 | //如果顶到最左端以后不允许缩小了,无法拖动。 500 | if(startLeft + mSelectWidth <= mLeftBlankWidth) { 501 | /*int windowLen = mWindowLen + distance; 502 | mWindowLen = windowLen; 503 | if(mWindowLen > mMaxWindowLen) { 504 | mWindowLen = mMaxWindowLen; 505 | } else if(mWindowLen < 0) { 506 | mWindowLen = 0; 507 | }*/ 508 | return; 509 | } else { 510 | mStartLeft = startLeft; 511 | } 512 | } 513 | 514 | invalidate(); 515 | 516 | if(mRangeChangedListener != null) { 517 | mRangeChangedListener.onRangeChanging(HINT_DIRECTION_RIGHT, rightStart); 518 | } 519 | } 520 | 521 | } 522 | 523 | protected int measureDimension(int defaultSize, int measureSpec) { 524 | 525 | int result; 526 | 527 | int specMode = MeasureSpec.getMode(measureSpec); 528 | int specSize = MeasureSpec.getSize(measureSpec); 529 | 530 | if (specMode == MeasureSpec.EXACTLY) { 531 | result = specSize; 532 | } else if (specMode == MeasureSpec.AT_MOST) { 533 | result = Math.min(defaultSize, specSize); 534 | } else { 535 | result = defaultSize; 536 | } 537 | 538 | return result; 539 | } 540 | 541 | @Override 542 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 543 | 544 | if (mSeekWidth == 0) { 545 | mSeekWidth = MeasureSpec.getSize(widthMeasureSpec); 546 | } 547 | 548 | int width = measureDimension(mSeekWidth, widthMeasureSpec); 549 | int height = measureDimension(mSelectHeight, heightMeasureSpec); 550 | 551 | setMeasuredDimension(width, height); 552 | } 553 | 554 | @Override 555 | protected void onDraw(Canvas canvas) { 556 | super.onDraw(canvas); 557 | 558 | drawOutsideLayer(canvas); 559 | if(mChangeType == CHANGE_TYPE_SCALE || (mChangeType == CHANGE_TYPE_MOVE && mLeftChange)) { 560 | drawLeftSelect(canvas); 561 | } else { 562 | drawLeftPointSelect(canvas); 563 | } 564 | drawCenterRect(canvas); 565 | if(mChangeType == CHANGE_TYPE_SCALE) { 566 | drawRightSelect(canvas); 567 | } else if(mChangeType == CHANGE_TYPE_MOVE) { 568 | drawRightPointSelect(canvas); 569 | } 570 | 571 | } 572 | 573 | @Override 574 | public boolean onTouchEvent(MotionEvent event) { 575 | if (mHorizontalScrollView != null) { 576 | mHorizontalScrollView.setRangeMoving(false); 577 | } 578 | if(!mCanChange) { 579 | return true; 580 | } 581 | switch (event.getAction()) { 582 | case MotionEvent.ACTION_DOWN: 583 | mHitState = getHit(event.getX(), event.getY()); 584 | if (mHitState != HINT_NONE) { 585 | mLastX = event.getX(); 586 | mLastY = event.getY(); 587 | mValidPointerId = event.getPointerId(event.getActionIndex()); 588 | if (mHorizontalScrollView != null) { 589 | mHorizontalScrollView.setRangeMoving(true); 590 | } 591 | } 592 | break; 593 | case MotionEvent.ACTION_UP: 594 | mHitState = HINT_NONE; 595 | if (mHorizontalScrollView != null) { 596 | mHorizontalScrollView.setRangeMoving(false); 597 | } 598 | if(mRangeChangedListener != null) { 599 | mRangeChangedListener.onRangeChangeFinished(); 600 | } 601 | break; 602 | case MotionEvent.ACTION_MOVE: 603 | if (event.getPointerId(event.getActionIndex()) == mValidPointerId && mHitState != HINT_NONE) { 604 | if(mChangeType == CHANGE_TYPE_SCALE) { 605 | handleMotion(mHitState, (int) (event.getX() - mLastX)); 606 | } else if(mChangeType == CHANGE_TYPE_MOVE) { 607 | handleMoveMotion(mHitState, (int) (event.getX() - mLastX)); 608 | } 609 | mLastX = event.getX(); 610 | mLastY = event.getY(); 611 | if (mHorizontalScrollView != null) { 612 | mHorizontalScrollView.setRangeMoving(true); 613 | } 614 | } 615 | break; 616 | } 617 | 618 | return true; 619 | } 620 | 621 | public interface OnRangeChangedListener { 622 | void onRangeChanging(int direction, int postion); 623 | void onRangeChangeFinished(); 624 | } 625 | } 626 | -------------------------------------------------------------------------------- /app/src/main/java/org/zhgeaits/risv/VideoImageSeekbar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Zhang Ge 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.zhgeaits.risv; 17 | 18 | import android.content.Context; 19 | import android.graphics.Color; 20 | import android.graphics.drawable.BitmapDrawable; 21 | import android.graphics.drawable.Drawable; 22 | import android.util.AttributeSet; 23 | import android.view.Gravity; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.ImageView; 28 | import android.widget.LinearLayout; 29 | import android.widget.RelativeLayout; 30 | 31 | import java.util.ArrayList; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | 36 | /** 37 | * Created by zhangge on 2015/9/24. 38 | * 区间范围选择控件 39 | */ 40 | public class VideoImageSeekbar extends RelativeLayout { 41 | 42 | private Context mContext; 43 | private View mRoot; 44 | private LayoutInflater mInflater; 45 | private LinearLayout mList; 46 | private RangeImageSelectedView mRangeView; 47 | private CustomHorizontalScrollView mHorizontalScrollView; 48 | private RelativeLayout mSeekbarContainer; 49 | private ImageView mSelectbar; //进度条 50 | private RelativeLayout.LayoutParams mSelectbarParams; //选择进度条的布局参数 51 | private MultiRangebar mRangebar; 52 | private MultiPointBar mPointbar; 53 | 54 | private Map mCacheRangeView; 55 | 56 | private int mImageBarLen; //图片的长度,进度条真正意义的像素长度 57 | private int mLeftBlankWidth; //左边空白处的宽度 58 | private int mRightBlankWidth; //右边空白出的宽度 59 | private int mSelectbarWidth; //中间竖条的宽度 60 | private int mCurrentProgress; //光标所在的位置,像素 61 | private int mMaxProgress = 100; //进度条的长度,不包含前后的空白,默认是100 62 | private int mMinSelectProgress = 0; //最少的窗口选择宽度,默认是0 63 | private int mImageHeight; //图片的宽度 64 | private int mImageWidth; //图片的高度 65 | private double mLastImageWidthPercent = 1.0d; //最后一张图片的宽度的百分比 66 | 67 | private OnSeekbarChangedListener mProgressChangeListener;//滚动的监听器 68 | 69 | public VideoImageSeekbar(Context context) { 70 | super(context); 71 | } 72 | 73 | public VideoImageSeekbar(Context context, AttributeSet attrs) { 74 | super(context, attrs); 75 | init(context, attrs, 0); 76 | } 77 | 78 | public VideoImageSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { 79 | super(context, attrs, defStyleAttr); 80 | init(context, attrs, defStyleAttr); 81 | } 82 | 83 | private void init(Context context, AttributeSet attrs, int defStyle) { 84 | mContext = context; 85 | mInflater = LayoutInflater.from(context); 86 | mRoot = mInflater.inflate(R.layout.layout_image_seekbar, this, true); 87 | mCacheRangeView = new HashMap<>(); 88 | 89 | mList = (LinearLayout) mRoot.findViewById(R.id.video_image_seekbar_list); 90 | mSelectbar = (ImageView) mRoot.findViewById(R.id.video_image_seekbar_selectbar); 91 | mHorizontalScrollView = (CustomHorizontalScrollView) mRoot.findViewById(R.id.video_image_horizontal_scrollview); 92 | mSeekbarContainer = (RelativeLayout) mRoot.findViewById(R.id.video_image_seekbar_container); 93 | 94 | mSelectbarParams = (LayoutParams) mSelectbar.getLayoutParams(); 95 | 96 | mHorizontalScrollView.setOnScrollListener(new CustomHorizontalScrollView.OnScrollListener() { 97 | 98 | @Override 99 | public void onScrollStop() { 100 | mCurrentProgress = mHorizontalScrollView.getScrollX() + mSelectbar.getLeft(); 101 | if (mProgressChangeListener != null) { 102 | int progress = (mCurrentProgress - mLeftBlankWidth) * mMaxProgress / mImageBarLen; 103 | mProgressChangeListener.onProgressChanged(progress, true); 104 | } 105 | } 106 | 107 | @Override 108 | public synchronized void onScrollChanged(int l, int t, int oldl, int oldt, boolean fromUser) { 109 | mCurrentProgress = l + mSelectbar.getLeft(); 110 | if (mProgressChangeListener != null) { 111 | int progress = (mCurrentProgress - mLeftBlankWidth) * mMaxProgress / mImageBarLen; 112 | mProgressChangeListener.onProgressChanged(progress, fromUser); 113 | } 114 | } 115 | }); 116 | } 117 | 118 | /** 119 | * 设置最大的进度值,一般设置为100 120 | * 121 | * @param maxProgress 122 | */ 123 | public void setMaxProgress(int maxProgress) { 124 | this.mMaxProgress = maxProgress; 125 | } 126 | 127 | /** 128 | * 获取当前的进度值,参考值是MaxProgress, 即返回值是0-100之间 129 | * 130 | * @return 131 | */ 132 | public double getCurrentProgress() { 133 | double progress = (double) (mCurrentProgress - mLeftBlankWidth) * mMaxProgress / (double) mImageBarLen; 134 | return progress; 135 | } 136 | 137 | /** 138 | * 设置当前进度 139 | * 140 | * @param progress 0 - 100之间的百分比 141 | */ 142 | public synchronized void setProgress(double progress) { 143 | 144 | //计算光标所在的像素位置 145 | int newProgress = (int) (progress * mImageBarLen / mMaxProgress + mLeftBlankWidth); 146 | 147 | if (newProgress == mCurrentProgress) { 148 | return; 149 | } 150 | 151 | //滚动到当前进度位置 152 | mHorizontalScrollView.smoothScrollTo(mHorizontalScrollView.getScrollX() + (newProgress - mCurrentProgress), 0); 153 | } 154 | 155 | /** 156 | * 设置最小的选择区间 157 | * 158 | * @param minWindowLen 159 | */ 160 | public void setMinWindowLen(int minWindowLen) { 161 | this.mMinSelectProgress = minWindowLen; 162 | } 163 | 164 | /** 165 | * 获取当前窗口最大值的百分比,0-100之间 166 | * @return 167 | */ 168 | public double getCurrentMaxWindowProgress() { 169 | if (mRangeView != null) { 170 | return mRangeView.getMaxWindowLen() * mMaxProgress / mImageBarLen; 171 | } 172 | return 0; 173 | } 174 | 175 | /** 176 | * 获取当前的最大窗口值 177 | * 178 | * @return 179 | */ 180 | public int getCurrentMaxWindowLen() { 181 | if (mRangeView != null) { 182 | return mRangeView.getMaxWindowLen(); 183 | } 184 | return 0; 185 | } 186 | 187 | /** 188 | * 设置当前选择框的最大值 189 | * 190 | * @param selectProgress 传值是0-100之间的百分比 191 | * @return 192 | */ 193 | public void setCurrentMaxWindowLen(int selectProgress) { 194 | int windowLen = selectProgress * mImageBarLen / mMaxProgress; 195 | if (mRangeView != null) { 196 | mRangeView.setMaxWindowLen(windowLen); 197 | } 198 | } 199 | 200 | /** 201 | * 获取当前的rang id 202 | * 203 | * @return 204 | */ 205 | public int getCurrentRangeId() { 206 | if (mRangeView != null) { 207 | return mRangeView.getId(); 208 | } 209 | return -1; 210 | } 211 | 212 | /** 213 | * 判断当前是否有选择框显示,参看invisibleRangeView方法,mRangeView为null的时候没有显示 214 | * 其他情况添加或者visible以后都不为空 215 | * 216 | * @return 217 | */ 218 | public boolean isRangeViewVisible() { 219 | if (mRangeView != null) { 220 | return true; 221 | } 222 | return false; 223 | } 224 | 225 | /** 226 | * 获取当前选择框的类型 227 | * 228 | * @return 229 | */ 230 | public int getCurrentRangeType() { 231 | if (mRangeView != null) { 232 | return mRangeView.getChangeType(); 233 | } 234 | return -1; 235 | } 236 | 237 | /** 238 | * 修改当前的rangview宽度 239 | * 240 | * @param selectProgress 241 | */ 242 | public void setCurrentRangeViewLen(int selectProgress) { 243 | if (mRangeView == null) { 244 | return; 245 | } 246 | 247 | int windowLen = selectProgress * mImageBarLen / mMaxProgress; 248 | mRangeView.setWindowLen(windowLen); 249 | mRangeView.requestLayout(); 250 | } 251 | 252 | /** 253 | * 修改当前rangeview的起始进度 254 | * 255 | * @param startProgress 256 | */ 257 | public void setCurrentRangeViewStart(double startProgress) { 258 | if (mRangeView == null) { 259 | return; 260 | } 261 | 262 | int windowStart = (int) (startProgress * mImageBarLen / mMaxProgress + mLeftBlankWidth); 263 | mRangeView.setWindowStart(windowStart); 264 | mRangeView.requestLayout(); 265 | } 266 | 267 | /** 268 | * 获取当前rangeView的起始进度 269 | * @return 参考值是MaxProgress, 即返回值是0-100之间 270 | */ 271 | public double getCurrentRangeViewStart() { 272 | if (mRangeView == null) { 273 | return -1; 274 | } 275 | 276 | int windowStart = mRangeView.getWindowStart(); 277 | double startProgress = (windowStart - mLeftBlankWidth) * mMaxProgress / mImageBarLen; 278 | return startProgress; 279 | } 280 | 281 | /** 282 | * 删除选择窗口 283 | * 284 | * @param id 285 | */ 286 | public void removeRangeView(int id) { 287 | mSeekbarContainer.removeView(mRangeView); 288 | mCacheRangeView.remove(id); 289 | mRangeView = null; 290 | if (mRangebar != null) { 291 | mRangebar.removeRange(id); 292 | } 293 | if (mPointbar != null) { 294 | mPointbar.removePoint(id); 295 | } 296 | } 297 | 298 | /** 299 | * 隐藏选择窗口,并显示选择进度条 300 | * 301 | * @param id 302 | */ 303 | public void invisibleRangeView(int id) { 304 | if (RangeImageSelectedView.CHANGE_TYPE_MOVE == getCurrentRangeType()) { 305 | addMarkPoint(id); 306 | } else { 307 | addMarkRange(id); 308 | } 309 | } 310 | 311 | /** 312 | * 显示选择窗口 313 | * 314 | * @param id 315 | */ 316 | public void visibleRangeView(int id) { 317 | if (mRangeView != null && mRangeView.getId() == id) { 318 | return; 319 | } 320 | 321 | if (mRangeView != null) { 322 | invisibleRangeView(mRangeView.getId()); 323 | } 324 | 325 | RangeImageSelectedView rangeView = mCacheRangeView.get(id); 326 | if (rangeView != null) { 327 | mRangeView = rangeView; 328 | 329 | mSeekbarContainer.addView(mRangeView); 330 | mRangebar.removeRange(id); 331 | mPointbar.removePoint(id); 332 | 333 | //广播进度条 334 | if (mProgressChangeListener != null) { 335 | //通知进度条的范围 336 | double startProgress = (double) (mRangeView.getWindowStart() - mLeftBlankWidth) / (double) mImageBarLen; 337 | double selectProgress = (double) mRangeView.getWindowLen() / (double) mImageBarLen; 338 | mProgressChangeListener.onRangeChanged(mRangeView.getId(), startProgress, selectProgress); 339 | } 340 | } 341 | } 342 | 343 | /** 344 | * 添加标记的选择范围 345 | * 346 | * @param id 347 | */ 348 | public void addMarkRange(int id) { 349 | mSeekbarContainer.removeView(mRangeView); 350 | 351 | if (mRangebar == null) { 352 | mRangebar = new MultiRangebar(mContext); 353 | mRangebar.setBarWidth(mList.getWidth()); 354 | RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 355 | params.addRule(RelativeLayout.BELOW, R.id.video_image_seekbar_list); 356 | params.topMargin = DimenConverter.dip2px(mContext, 1); 357 | mRangebar.setLayoutParams(params); 358 | mSeekbarContainer.addView(mRangebar); 359 | } 360 | mRangebar.addRange(id, mRangeView.getWindowStart(), mRangeView.getWindowStart() + mRangeView.getWindowLen()); 361 | 362 | mRangeView = null; 363 | } 364 | 365 | /** 366 | * 显示标记的点 367 | * 368 | * @param id 369 | */ 370 | public void addMarkPoint(int id) { 371 | mSeekbarContainer.removeView(mRangeView); 372 | 373 | int color = Color.parseColor("#FF8900"); 374 | if (mPointbar == null) { 375 | mPointbar = new MultiPointBar(mContext); 376 | mPointbar.setBarWidth(mList.getWidth()); 377 | RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 378 | params.addRule(RelativeLayout.ABOVE, R.id.video_image_seekbar_list); 379 | mPointbar.setLayoutParams(params); 380 | mSeekbarContainer.addView(mPointbar); 381 | } 382 | mPointbar.addPoint(id, mRangeView.getWindowStart(), color); 383 | 384 | mRangeView = null; 385 | } 386 | 387 | 388 | /** 389 | * 添加标签的选择框 390 | * 391 | * @param id 392 | * @param startProgress 393 | * @param selectProgress 394 | */ 395 | public void addScaledRangeView(int id, double startProgress, int selectProgress) { 396 | addRangeView(id, startProgress, selectProgress, RangeImageSelectedView.CHANGE_TYPE_SCALE, 0); 397 | } 398 | 399 | /** 400 | * 添加弹幕的选择框 401 | * 402 | * @param id 403 | * @param startProgress 404 | * @param selectProgress 405 | */ 406 | public void addFixedRangeView(int id, double startProgress, int selectProgress, int maxWindowProgress) { 407 | addRangeView(id, startProgress, selectProgress, RangeImageSelectedView.CHANGE_TYPE_MOVE, maxWindowProgress); 408 | } 409 | 410 | /** 411 | * 添加一个区间选择器,添加的时候往外广播一下区间的范围 412 | * 413 | * @param startProgress 这两个进度都是0-100之间的值 414 | * @param selectProgress 415 | */ 416 | public void addRangeView(int id, double startProgress, int selectProgress, int changeType, int maxWindowSelect) { 417 | if (mRangeView != null && mRangeView.getId() == id) { 418 | return; 419 | } 420 | 421 | if (selectProgress < 0) { 422 | return; 423 | } 424 | 425 | if (mRangeView != null) { 426 | invisibleRangeView(mRangeView.getId()); 427 | } 428 | 429 | mRangeView = new RangeImageSelectedView(mContext); 430 | mRangeView.setId(id); 431 | mRangeView.setChangeType(changeType); 432 | 433 | mRangeView.setHorizontalScrollView(mHorizontalScrollView); 434 | mRangeView.setSeekWidth(mList.getWidth()); 435 | mRangeView.setSeekbarVisiblewidth(getWidth()); 436 | mRangeView.setLeftBlankWidth(mLeftBlankWidth); 437 | mRangeView.setRightBlankWidth(mRightBlankWidth); 438 | mRangeView.setImageListLen(mImageBarLen); 439 | 440 | int windowStart = (int) (startProgress * mImageBarLen / (double) mMaxProgress + mLeftBlankWidth); 441 | int windowLen = selectProgress * mImageBarLen / mMaxProgress; 442 | int minWindowLen = mMinSelectProgress * mImageBarLen / mMaxProgress; 443 | int maxWindowLen = maxWindowSelect * mImageBarLen / mMaxProgress; 444 | mRangeView.setWindowStart(windowStart); 445 | mRangeView.setWindowLen(windowLen); 446 | //最小宽度,默认是0 447 | mRangeView.setMinWindowLen(minWindowLen); 448 | 449 | //todo 450 | //如果是固定的选择框才会有最大值 451 | if (changeType == RangeImageSelectedView.CHANGE_TYPE_MOVE) { 452 | if (maxWindowLen > 0) { 453 | mRangeView.setMaxWindowLen(maxWindowLen); 454 | if(maxWindowSelect > selectProgress) { 455 | mRangeView.setLeftChange(true); 456 | } 457 | } else { 458 | mRangeView.setMaxWindowLen(windowLen); 459 | } 460 | } 461 | 462 | mCacheRangeView.put(id, mRangeView); 463 | 464 | mSeekbarContainer.addView(mRangeView); 465 | mRangeView.setOnRangeChangedListener(new RangeImageSelectedView.OnRangeChangedListener() { 466 | @Override 467 | public void onRangeChanging(int direction, int postion) { 468 | 469 | //移动进度条的光标 470 | mCurrentProgress = postion; 471 | mSelectbarParams = new RelativeLayout.LayoutParams(mSelectbarWidth, ViewGroup.LayoutParams.MATCH_PARENT); 472 | mSelectbarParams.leftMargin = postion - mHorizontalScrollView.getScrollX(); 473 | mSelectbar.setLayoutParams(mSelectbarParams); 474 | mSelectbar.requestLayout(); 475 | 476 | //广播进度条 477 | if (mProgressChangeListener != null) { 478 | double progress = (double) (mCurrentProgress - mLeftBlankWidth) * mMaxProgress / (double) mImageBarLen; 479 | double startProgress = (double) (mRangeView.getWindowStart() - mLeftBlankWidth) / (double) mImageBarLen; 480 | double selectProgress = (double) mRangeView.getWindowLen() / (double) mImageBarLen; 481 | 482 | //通知进度条的范围 483 | mProgressChangeListener.onRangeChanged(mRangeView.getId(), startProgress, selectProgress); 484 | 485 | //通知光标百分比 486 | mProgressChangeListener.onProgressChanged(progress, true); 487 | } 488 | } 489 | 490 | @Override 491 | public void onRangeChangeFinished() { 492 | 493 | //光标回到中间 494 | mSelectbarParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 495 | mSelectbar.setLayoutParams(mSelectbarParams); 496 | mSelectbar.requestLayout(); 497 | mHorizontalScrollView.smoothScrollTo(mCurrentProgress - getWidth() / 2, 0); 498 | } 499 | 500 | }); 501 | 502 | //广播进度条 503 | if (mProgressChangeListener != null) { 504 | //通知进度条的范围 505 | mProgressChangeListener.onRangeChanged(mRangeView.getId(), startProgress / 100.0d, selectProgress / 100.0d); 506 | } 507 | } 508 | 509 | 510 | /** 511 | * 设置最后一张图片宽度的百分比 512 | * 513 | * @param percent 514 | */ 515 | public void setLastImageWidthPercent(double percent) { 516 | mLastImageWidthPercent = percent; 517 | } 518 | 519 | private void setImageList0(List lists) { 520 | mList.removeAllViews(); 521 | 522 | mImageHeight = DimenConverter.dip2px(mContext, 36); 523 | mImageWidth = DimenConverter.dip2px(mContext, 27); 524 | mSelectbarWidth = DimenConverter.dip2px(mContext, 4); 525 | mLeftBlankWidth = mSelectbar.getLeft(); 526 | mRightBlankWidth = mLeftBlankWidth + mSelectbarWidth; 527 | mCurrentProgress = mLeftBlankWidth; 528 | 529 | View leftBlank = new View(mContext); 530 | ViewGroup.LayoutParams leftParam = new RelativeLayout.LayoutParams(mLeftBlankWidth, ViewGroup.LayoutParams.MATCH_PARENT); 531 | leftBlank.setLayoutParams(leftParam); 532 | mList.addView(leftBlank); 533 | 534 | mImageBarLen = 0; 535 | for (int i = 0; i < lists.size(); i++) { 536 | 537 | ImageView imageView = new ImageView(mContext); 538 | 539 | int destWidth = mImageWidth; 540 | if (i == lists.size() - 1) { 541 | destWidth = (int) (mImageWidth * mLastImageWidthPercent); 542 | } 543 | 544 | LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(destWidth, mImageHeight); 545 | params.gravity = Gravity.CENTER_VERTICAL; 546 | imageView.setLayoutParams(params); 547 | imageView.setBackgroundDrawable(lists.get(i)); 548 | mList.addView(imageView); 549 | mImageBarLen += destWidth; 550 | } 551 | 552 | View rightBlank = new View(mContext); 553 | ViewGroup.LayoutParams rightParam = new RelativeLayout.LayoutParams(mRightBlankWidth, ViewGroup.LayoutParams.MATCH_PARENT); 554 | rightBlank.setLayoutParams(rightParam); 555 | mList.addView(rightBlank); 556 | 557 | mHorizontalScrollView.setBarLength(getWidth()); 558 | mHorizontalScrollView.setImageBarLen(mImageBarLen); 559 | mHorizontalScrollView.setSeekWidth(mImageBarLen + mLeftBlankWidth + mRightBlankWidth); 560 | } 561 | 562 | /** 563 | * 设置图片背景 564 | * @param paths 565 | */ 566 | public void setImageListPath(List paths) { 567 | List lists = new ArrayList<>(); 568 | for(int i = 0; i < paths.size(); i ++) { 569 | Drawable drawable = BitmapDrawable.createFromPath(paths.get(i)); 570 | lists.add(drawable); 571 | } 572 | setImageList0(lists); 573 | } 574 | 575 | /** 576 | * 设置图片背景 577 | * @param ids 578 | */ 579 | public void setImageListIds(List ids) { 580 | List lists = new ArrayList<>(); 581 | for(int i = 0; i < ids.size(); i ++) { 582 | Drawable drawable = getResources().getDrawable(ids.get(i)); 583 | lists.add(drawable); 584 | } 585 | setImageList0(lists); 586 | } 587 | 588 | public void setOnProgressChangeListener(OnSeekbarChangedListener listener) { 589 | this.mProgressChangeListener = listener; 590 | } 591 | 592 | public interface OnSeekbarChangedListener { 593 | void onProgressChanged(double progress, boolean fromUser); 594 | void onRangeChanged(int id, double startPercent, double selectPercent); 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_bar_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_bar_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/img_bar_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/left_arrow_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/multi_range_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/right_arrow_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 17 | 18 | 22 | 23 |