├── .gitignore
├── LICENSE
├── OptionBarView
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── dmingo
│ │ └── optionbarview
│ │ └── OptionBarView.java
│ └── res
│ └── values
│ └── attrs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dmingo
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── dmingo
│ │ │ └── sample
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ └── ic_vector.xml
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── dmingo
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 DMingOu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/OptionBarView/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/OptionBarView/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.github.dcendents.android-maven'
3 | group='com.github.DMingOu'
4 |
5 |
6 | android {
7 | compileSdkVersion 30
8 |
9 | defaultConfig {
10 | minSdkVersion 14
11 | targetSdkVersion 30
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 |
28 | dependencies {
29 | }
30 |
--------------------------------------------------------------------------------
/OptionBarView/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/OptionBarView/consumer-rules.pro
--------------------------------------------------------------------------------
/OptionBarView/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
--------------------------------------------------------------------------------
/OptionBarView/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | /
5 |
--------------------------------------------------------------------------------
/OptionBarView/src/main/java/com/dmingo/optionbarview/OptionBarView.java:
--------------------------------------------------------------------------------
1 | package com.dmingo.optionbarview;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.content.res.Resources;
7 | import android.content.res.TypedArray;
8 | import android.graphics.Bitmap;
9 | import android.graphics.BitmapFactory;
10 | import android.graphics.Canvas;
11 | import android.graphics.Color;
12 | import android.graphics.Paint;
13 | import android.graphics.PaintFlagsDrawFilter;
14 | import android.graphics.Rect;
15 | import android.graphics.RectF;
16 | import android.graphics.drawable.Drawable;
17 | import android.os.Build;
18 | import android.util.AttributeSet;
19 | import android.util.Log;
20 | import android.util.TypedValue;
21 | import android.view.MotionEvent;
22 | import android.view.View;
23 | import android.widget.Checkable;
24 |
25 |
26 | /**
27 | * @description: 条目类型View
28 | * @author: DMingO
29 | */
30 | public class OptionBarView extends View implements Checkable {
31 |
32 | private static final String TAG = "OptionBarView";
33 |
34 | /**
35 | * 控件的宽
36 | */
37 | private int mWidth;
38 | /**
39 | * 控件的高
40 | */
41 | private int mHeight;
42 |
43 | private Context mContext;
44 |
45 | /**
46 | * 左图bitmap
47 | */
48 | private Bitmap leftImage;
49 | /**
50 | * 右图bitmap
51 | */
52 | private Bitmap rightImage;
53 |
54 | /**
55 | * 判断是否显示控件
56 | */
57 | private boolean isShowLeftImg = true;
58 | private boolean isShowLeftText = true;
59 | private boolean isShowRightView = true;
60 | private boolean isShowRightText = true;
61 |
62 | //拆分模式(默认是false,也就是一个整体)
63 | private boolean mSplitMode = false;
64 | /**
65 | * 判断按下开始的位置是否在左
66 | */
67 | private boolean leftStartTouchDown = false;
68 | /**
69 | * 判断按下开始的位置是否在中间
70 | */
71 | private boolean centerStartTouchDown = false;
72 | /**
73 | * 判断按下开始的位置是否在右
74 | */
75 | private boolean rightStartTouchDown = false;
76 | /**
77 | * 标题
78 | */
79 | private String title = "";
80 | /**
81 | * 标题字体大小
82 | */
83 | private float titleTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
84 | 16, getResources().getDisplayMetrics());
85 | /**
86 | * 标题颜色
87 | */
88 | private int titleTextColor = Color.BLACK;
89 | /**
90 | * 左边文字
91 | */
92 | private String leftText = "";
93 | /**
94 | * 左边文字大小
95 | */
96 | private float leftTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
97 | 16, getResources().getDisplayMetrics());
98 | /**
99 | * 左字左边距
100 | */
101 | private int leftTextMarginLeft = -1;
102 | /**
103 | * 左图左边距
104 | */
105 | private int leftImageMarginLeft = -1;
106 | /**
107 | * 左图右边距
108 | */
109 | private int leftImageMarginRight = -1;
110 | /**
111 | * 左边文字颜色
112 | */
113 | private int leftTextColor = Color.BLACK;
114 | /**
115 | * 右边文字
116 | */
117 | private String rightText = "";
118 | /**
119 | * 右边文字大小
120 | */
121 | private float rightTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
122 | 16, getResources().getDisplayMetrics());
123 | /**
124 | * 右边文字颜色
125 | */
126 | private int rightTextColor = Color.BLACK;
127 | /**
128 | * 右字右边距
129 | */
130 | private int rightTextMarginRight = -1;
131 | /**
132 | * 右View的左边距
133 | */
134 | private int rightViewMarginLeft = -1;
135 | /**
136 | * 右图的右边距
137 | */
138 | private int rightViewMarginRight = -1;
139 |
140 | /**
141 | * 左图的 宽度大小
142 | */
143 | private int leftImageWidth = -1;
144 |
145 | private int leftImageHeight = -1;
146 |
147 | private int rightImageWidth = -1;
148 |
149 | private int rightImageHeight = -1;
150 |
151 | private Paint mPaint;
152 | /**
153 | * 对文本的约束
154 | */
155 | private Rect mTextBound;
156 |
157 | /**
158 | * 控制整体布局,可复用绘制其他组件
159 | */
160 | private Rect optionRect;
161 |
162 | /**
163 | * 是否绘制分隔线
164 | */
165 | private Boolean isShowDivideLine = false;
166 |
167 | //分割线左、 右边距
168 | private int divide_line_left_margin = 0;
169 |
170 | private int divide_line_right_margin = 0;
171 | //分割线 颜色
172 | private int divide_line_color = Color.parseColor("#DCDCDC");
173 |
174 | //分割线高度 默认为1px
175 | private int divide_line_height = 1;
176 |
177 | //分割线的位置是否在上方
178 | private boolean divide_line_top_gravity = false;
179 |
180 | /**
181 | * 绘制分割线的画笔
182 | */
183 | private Paint dividePaint;
184 |
185 | /**
186 | * 左区域的右边界
187 | */
188 | private int leftBound;
189 |
190 | /**
191 | * 右区域的左边界
192 | */
193 | private int rightBound;
194 |
195 |
196 | private int rightViewType = -1;
197 |
198 | /**
199 | * 点击完整的条目的事件回调
200 | */
201 | private OnOptionItemClickListener optionItemClickListener;
202 |
203 |
204 |
205 |
206 |
207 |
208 | private PaintFlagsDrawFilter paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
209 |
210 | public OptionBarView(Context context, AttributeSet attrs) {
211 | super(context, attrs);
212 | this.mContext = context;
213 |
214 | ///////////////////////////////////////////////////////////////////////////////////////////////
215 | // //
216 | // 获取自定义属性的值 //
217 | // //
218 | ///////////////////////////////////////////////////////////////////////////////////////////////
219 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.OptionBarView);
220 | isShowDivideLine = optBoolean(typedArray ,R.styleable.OptionBarView_show_divide_line,false);
221 | divide_line_top_gravity = optBoolean(typedArray ,R.styleable.OptionBarView_divide_line_top_gravity,false);
222 | divide_line_color = optColor(typedArray ,R.styleable.OptionBarView_divide_line_color,Color.parseColor("#DCDCDC"));
223 | divide_line_height = optPixelSize(typedArray , R.styleable.OptionBarView_divide_line_height ,1);
224 | divide_line_left_margin = optPixelSize(typedArray , R.styleable.OptionBarView_divide_line_left_margin,dp2px(0));
225 | divide_line_right_margin = optPixelSize(typedArray , R.styleable.OptionBarView_divide_line_right_margin,dp2px(0));
226 | leftText = optString(typedArray,R.styleable.OptionBarView_left_text,"");
227 | rightText = optString(typedArray,R.styleable.OptionBarView_right_text,"");
228 | title = optString(typedArray,R.styleable.OptionBarView_title,"");
229 | titleTextSize = optPixelSize(typedArray ,R.styleable.OptionBarView_title_size , sp2px(-1));
230 | titleTextColor = optColor(typedArray , R.styleable.OptionBarView_title_color , Color.BLACK);
231 | leftTextMarginLeft = optPixelSize(typedArray,R.styleable.OptionBarView_left_text_margin_left,dp2px(-1));
232 | rightTextMarginRight = optPixelSize(typedArray,R.styleable.OptionBarView_right_text_margin_right,dp2px(-1));
233 | leftImageWidth = optPixelSize(typedArray ,R.styleable.OptionBarView_left_src_height , dp2px(-1));
234 | rightImageWidth = optPixelSize(typedArray ,R.styleable.OptionBarView_right_src_height , dp2px(-1));
235 | leftImageHeight = optPixelSize(typedArray ,R.styleable.OptionBarView_left_src_width , dp2px(-1));
236 | rightImageHeight = optPixelSize(typedArray ,R.styleable.OptionBarView_right_src_width , dp2px(-1));
237 | rightTextSize = optPixelSize(typedArray ,R.styleable.OptionBarView_right_text_size ,sp2px(16));
238 | leftTextSize = optPixelSize(typedArray ,R.styleable.OptionBarView_left_text_size ,sp2px(16));
239 | leftImageMarginLeft = optPixelSize(typedArray , R.styleable.OptionBarView_left_image_margin_left,dp2px(-1));
240 | rightViewMarginLeft = optPixelSize(typedArray , R.styleable.OptionBarView_right_view_margin_left,dp2px(-1));
241 | leftImageMarginRight = optPixelSize(typedArray , R.styleable.OptionBarView_left_image_margin_right,dp2px(-1));
242 | rightViewMarginRight = optPixelSize(typedArray , R.styleable.OptionBarView_right_view_margin_right,dp2px(-1));
243 | leftTextColor = optColor(typedArray,R.styleable.OptionBarView_left_text_color , Color.BLACK);
244 | rightTextColor = optColor(typedArray,R.styleable.OptionBarView_right_text_color, Color.BLACK);
245 | mSplitMode = optBoolean(typedArray,R.styleable.OptionBarView_split_mode,false);
246 | rightViewType = optInt(typedArray,R.styleable.OptionBarView_rightViewType,-1);
247 |
248 | int leftImageId = optResourceId(typedArray , R.styleable.OptionBarView_left_src,0);
249 | if(leftImageId != 0){
250 | leftImage = BitmapFactory.decodeResource(getResources(), leftImageId);
251 | //需要加载Vector资源
252 | if (leftImage == null) {
253 | Bitmap vectorBitmap = decodeVectorToBitmap(leftImageId);
254 | if (vectorBitmap != null) {
255 | leftImage = vectorBitmap;
256 | }
257 | }
258 | }
259 |
260 | if(rightViewType == RightViewType.IMAGE) {
261 | //获取定义的右侧图片ResId属性
262 | int rightImageId = optResourceId(typedArray,R.styleable.OptionBarView_right_src,0);
263 | if(rightImageId != 0){
264 | rightImage = BitmapFactory.decodeResource(getResources(), rightImageId);
265 | //需要加载Vector资源
266 | if (rightImage == null) {
267 | Bitmap vectorBitmap = decodeVectorToBitmap(optResourceId(typedArray,R.styleable.OptionBarView_right_src,0));
268 | if (vectorBitmap != null) {
269 | rightImage = vectorBitmap;
270 | }
271 | }
272 | }
273 |
274 | }
275 |
276 | ///////////////////////////////////////////////////////////////////////////////////////////////
277 | // //
278 | // Switch 获取初始参数 //
279 | // //
280 | ///////////////////////////////////////////////////////////////////////////////////////////////
281 | if(rightViewType == RightViewType.SWITCH){
282 | switchBackgroundWidth = optPixelSize(typedArray,R.styleable.OptionBarView_switch_background_width,dp2px(45f));
283 |
284 | switchBackgroundHeight = optPixelSize(typedArray,R.styleable.OptionBarView_switch_background_height,dp2px(25f));
285 |
286 | switchShadowEffect = optBoolean(typedArray,
287 | R.styleable.OptionBarView_switch_shadow_effect,
288 | true);
289 |
290 | uncheckSwitchCircleColor = optColor(typedArray,
291 | R.styleable.OptionBarView_switch_uncheckcircle_color,
292 | 0XffAAAAAA);
293 | uncheckCircleWidth = optPixelSize(typedArray,
294 | R.styleable.OptionBarView_switch_uncheckcircle_width,
295 | dp2px(1.5f));
296 | uncheckCircleOffsetX = dp2px(10);
297 | uncheckSwitchCircleRadius = optPixelSize(typedArray,
298 | R.styleable.OptionBarView_switch_uncheckcircle_radius,
299 | dp2px(4));//dp2px(4);
300 | switchCheckedLineOffsetX = dp2px(4);
301 | switchCheckedLineOffsetY = dp2px(4);
302 | switchShadowRadius = optPixelSize(typedArray,
303 | R.styleable.OptionBarView_switch_shadow_radius,
304 | dp2px(2.5f));//dp2px(2.5f);
305 |
306 | switchShadowOffset = optPixelSize(typedArray,
307 | R.styleable.OptionBarView_switch_shadow_offset,
308 | dp2px(1.5f));//dp2px(1.5f);
309 |
310 | switchShadowColor = optColor(typedArray,
311 | R.styleable.OptionBarView_switch_shadow_color,
312 | 0X33000000);//0X33000000;
313 |
314 | uncheckColor = optColor(typedArray,
315 | R.styleable.OptionBarView_switch_uncheck_color,
316 | 0XffDDDDDD);//0XffDDDDDD;
317 |
318 | switchCheckedColor = optColor(typedArray,
319 | R.styleable.OptionBarView_switch_checked_color,
320 | 0Xff51d367);//0Xff51d367;
321 |
322 | switchBorderWidth = optPixelSize(typedArray,
323 | R.styleable.OptionBarView_switch_border_width,
324 | dp2px(1));//dp2px(1);
325 |
326 | checkIndicatorLineColor = optColor(typedArray,
327 | R.styleable.OptionBarView_switch_checkline_color,
328 | Color.TRANSPARENT);//Color.TRANSPARENT;
329 |
330 | checkIndicatorLineWidth = optPixelSize(typedArray,
331 | R.styleable.OptionBarView_switch_checkline_width,
332 | dp2px(1f));//dp2px(1.0f);
333 |
334 | checkLineLength = dp2px(6);
335 |
336 | int buttonColor = optColor(typedArray,
337 | R.styleable.OptionBarView_switch_button_color,
338 | Color.WHITE);//Color.WHITE;
339 | //未选和已选的圆形按钮颜色默认情况都是白色
340 | uncheckButtonColor = optColor(typedArray,
341 | R.styleable.OptionBarView_switch_uncheckbutton_color,
342 | buttonColor);
343 |
344 | checkedButtonColor = optColor(typedArray,
345 | R.styleable.OptionBarView_switch_checkedbutton_color,
346 | buttonColor);
347 |
348 | int effectDuration = optInt(typedArray,
349 | R.styleable.OptionBarView_switch_effect_duration,
350 | 300);//300;
351 |
352 | isSwitchChecked = optBoolean(typedArray,
353 | R.styleable.OptionBarView_switch_checked,
354 | false);
355 |
356 | showSwitchIndicator = optBoolean(typedArray,
357 | R.styleable.OptionBarView_switch_show_indicator,
358 | true);
359 |
360 | uncheckSwitchBackground = optColor(typedArray,
361 | R.styleable.OptionBarView_switch_background,
362 | Color.WHITE);//Color.WHITE;
363 |
364 | enableSwitchAnimate = optBoolean(typedArray,
365 | R.styleable.OptionBarView_switch_enable_effect,
366 | true);
367 |
368 | switchBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
369 | switchButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
370 | switchButtonPaint.setColor(buttonColor);
371 |
372 | if(switchShadowEffect){
373 | switchButtonPaint.setShadowLayer(
374 | switchShadowRadius,
375 | 0, switchShadowOffset,
376 | switchShadowColor);
377 | }
378 | //创建一个Switch的背景的圆角矩形
379 | switchBackgroundRect = new RectF();
380 |
381 | //设置切换前后状态动画记录变量
382 | switchCurrentViewState = new ViewState();
383 | beforeState = new ViewState();
384 | afterState = new ViewState();
385 |
386 | //初始化动画执行器
387 | switchValueAnimator = ValueAnimator.ofFloat(0f, 1f);
388 | switchValueAnimator.setDuration(effectDuration);
389 | switchValueAnimator.setRepeatCount(0);
390 |
391 | switchValueAnimator.addUpdateListener(animatorUpdateListener);
392 | switchValueAnimator.addListener(switchAnimatorListener);
393 |
394 | super.setClickable(true);
395 | this.setPadding(0, 0, 0, 0);
396 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
397 | setLayerType(LAYER_TYPE_SOFTWARE, null);
398 | }
399 | }
400 |
401 | //参数获取完毕后 回收typeArray
402 | typedArray.recycle();
403 |
404 | optionRect = new Rect();
405 | mPaint = new Paint();
406 | mTextBound = new Rect();
407 | // 计算了描绘字体需要的范围
408 | mPaint.getTextBounds(title, 0, title.length(), mTextBound);
409 |
410 | //右侧图片非空则设置右侧为图片类型
411 | if(rightImage != null){
412 | rightViewType = RightViewType.IMAGE;
413 | }
414 |
415 | //初始化 分割线 画笔
416 | dividePaint = new Paint();
417 | dividePaint.setColor(divide_line_color);
418 |
419 | }
420 |
421 | @Override
422 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
423 | if(rightViewType == RightViewType.SWITCH){
424 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
425 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
426 |
427 | if(widthMode == MeasureSpec.UNSPECIFIED
428 | || widthMode == MeasureSpec.AT_MOST){
429 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_SWITCH_BACKGROUND_WIDTH, MeasureSpec.EXACTLY);
430 | }
431 | if(heightMode == MeasureSpec.UNSPECIFIED
432 | || heightMode == MeasureSpec.AT_MOST){
433 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_SWITCH_BACKGROUND_HEIGHT, MeasureSpec.EXACTLY);
434 | }
435 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
436 | }
437 |
438 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
439 | }
440 |
441 | @Override
442 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
443 | super.onSizeChanged(w, h, oldw, oldh);
444 |
445 | //右侧View为 Switch状态
446 | if(rightViewType == RightViewType.SWITCH){
447 | float viewPadding = Math.max(switchShadowRadius + switchShadowOffset, switchBorderWidth);
448 | switchBackgroundRight = w - (rightViewMarginRight >= 0 ? rightViewMarginRight : (float)mWidth / 32)-viewPadding;
449 | switchBackgroundLeft = switchBackgroundRight - switchBackgroundWidth + viewPadding;
450 | switchBackgroundTop = (float) ( h - switchBackgroundHeight) / 2 + viewPadding;
451 | switchBackgroundBottom = switchBackgroundHeight + switchBackgroundTop - viewPadding;
452 | //计算背景圆弧的半径
453 | viewRadius = (switchBackgroundBottom - switchBackgroundTop) * .5f;
454 |
455 | //按钮的半径
456 | buttonRadius = viewRadius - switchBorderWidth;
457 |
458 |
459 | centerX = (switchBackgroundLeft+ switchBackgroundRight) * .5f;
460 | centerY = (switchBackgroundTop + switchBackgroundBottom) * .5f;
461 |
462 | switchButtonMinX = switchBackgroundLeft+ viewRadius;
463 | switchButtonMaxX = switchBackgroundRight - viewRadius;
464 |
465 | if(isChecked()){
466 | setCheckedViewState(switchCurrentViewState);
467 | }else{
468 | setUncheckViewState(switchCurrentViewState);
469 | }
470 |
471 | isSwitchInit = true;
472 |
473 | postInvalidate();
474 | }
475 | }
476 |
477 |
478 |
479 |
480 |
481 | @Override
482 | protected void onDraw(Canvas canvas) {
483 | super.onDraw(canvas);
484 | mWidth = getWidth();
485 | mHeight = getHeight();
486 | leftBound = 0;
487 | rightBound = Integer.MAX_VALUE;
488 |
489 | //抗锯齿处理
490 | canvas.setDrawFilter(paintFlagsDrawFilter);
491 |
492 | optionRect.left = getPaddingLeft();
493 | optionRect.right = mWidth - getPaddingRight();
494 | optionRect.top = getPaddingTop();
495 | optionRect.bottom = mHeight - getPaddingBottom();
496 | //抗锯齿
497 | mPaint.setAntiAlias(true);
498 | mPaint.setTextSize(titleTextSize > leftTextSize ? Math.max(titleTextSize, rightTextSize) : Math.max(leftTextSize, rightTextSize));
499 | // mPaint.setTextSize(titleTextSize);
500 | mPaint.setStyle(Paint.Style.FILL);
501 | //文字水平居中
502 | mPaint.setTextAlign(Paint.Align.CENTER);
503 |
504 | //计算垂直居中baseline
505 | Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
506 | int baseLine = (int) ((optionRect.bottom + optionRect.top - fontMetrics.bottom - fontMetrics.top) / 2);
507 |
508 | float distance=(fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
509 | float baseline = optionRect.centerY()+distance;
510 |
511 | if (!title.trim().equals("")) {
512 | // 正常情况,将字体居中
513 | mPaint.setColor(titleTextColor);
514 | canvas.drawText(title, optionRect.centerX(), baseline, mPaint);
515 | optionRect.bottom -= mTextBound.height();
516 | }
517 |
518 |
519 | if (leftImage != null && isShowLeftImg) {
520 | // 计算左图范围
521 | optionRect.left = leftImageMarginLeft >= 0 ? leftImageMarginLeft : mWidth / 32;
522 | //计算 左右边界坐标值,若有设置左图偏移则使用,否则使用View的宽度/32
523 | if(leftImageWidth >= 0){
524 | optionRect.right = optionRect.left + leftImageWidth;
525 | }else {
526 | optionRect.right = optionRect.right + mHeight / 2;
527 | }
528 | //计算左图 上下边界的坐标值,若无设置右图高度,默认为高度的 1/2
529 | if(leftImageHeight >= 0){
530 | optionRect.top = ( mHeight - leftImageHeight) / 2;
531 | optionRect.bottom = leftImageHeight + optionRect.top;
532 | }else {
533 | optionRect.top = mHeight / 4;
534 | optionRect.bottom = mHeight * 3 / 4;
535 | }
536 | canvas.drawBitmap(leftImage, null, optionRect, mPaint);
537 |
538 | //有左侧图片,更新左区域的边界
539 | leftBound = Math.max(leftBound ,optionRect.right);
540 | }
541 | if (rightImage != null && isShowRightView && rightViewType == RightViewType.IMAGE) {
542 | // 计算右图范围
543 | //计算 左右边界坐标值,若有设置右图偏移则使用,否则使用View的宽度/32
544 | optionRect.right = mWidth - (rightViewMarginRight >= 0 ? rightViewMarginRight : mWidth / 32);
545 | if(rightImageWidth >= 0){
546 | optionRect.left = optionRect.right - rightImageWidth;
547 | }else {
548 | optionRect.left = optionRect.right - mHeight / 2;
549 | }
550 | //计算右图 上下边界的坐标值,若无设置右图高度,默认为高度的 1/2
551 | if(rightImageHeight >= 0){
552 | optionRect.top = ( mHeight - rightImageHeight) / 2;
553 | optionRect.bottom = rightImageHeight + optionRect.top;
554 | }else {
555 | optionRect.top = mHeight / 4;
556 | optionRect.bottom = mHeight * 3 / 4;
557 | }
558 | canvas.drawBitmap(rightImage, null, optionRect, mPaint);
559 |
560 | //右侧图片,更新右区域边界
561 | rightBound = Math.min(rightBound , optionRect.left);
562 | }
563 | if (leftText != null && !leftText.equals("") && isShowLeftText) {
564 | mPaint.setTextSize(leftTextSize);
565 | mPaint.setColor(leftTextColor);
566 | int w = 0;
567 | if (leftImage != null) {
568 | w += leftImageMarginLeft >= 0 ? leftImageMarginLeft : (mHeight / 8);//增加左图左间距
569 | w += mHeight / 2;//图宽
570 | w += leftImageMarginRight >= 0 ? leftImageMarginRight : (mWidth / 32);// 增加左图右间距
571 | w += Math.max(leftTextMarginLeft, 0);//增加左字左间距
572 | } else {
573 | w += leftTextMarginLeft >= 0 ? leftTextMarginLeft : (mWidth / 32);//增加左字左间距
574 | }
575 |
576 | mPaint.setTextAlign(Paint.Align.LEFT);
577 | // 计算了描绘字体需要的范围
578 | mPaint.getTextBounds(leftText, 0, leftText.length(), mTextBound);
579 |
580 | canvas.drawText(leftText, w, baseline, mPaint);
581 | //有左侧文字,更新左区域的边界
582 | leftBound = Math.max(w + mTextBound.width() , leftBound);
583 | }
584 | if (rightText != null && !rightText.equals("") && isShowRightText) {
585 | mPaint.setTextSize(rightTextSize);
586 | mPaint.setColor(rightTextColor);
587 |
588 | int w = mWidth;
589 | //文字右侧有View
590 | if (rightViewType != -1) {
591 | w -= rightViewMarginRight >= 0 ? rightViewMarginRight : (mHeight / 8);//增加右图右间距
592 | w -= rightViewMarginLeft >= 0 ? rightViewMarginLeft : (mWidth / 32);//增加右图左间距
593 | w -= Math.max(rightTextMarginRight, 0);//增加右字右间距
594 | //扣去右侧View的宽度
595 | if(rightViewType == RightViewType.IMAGE){
596 | w -= (optionRect.right - optionRect.left);
597 | }else if(rightViewType == RightViewType.SWITCH){
598 | w -= (switchBackgroundRight - switchBackgroundLeft + viewRadius * .5f);
599 | }
600 | } else {
601 | w -= rightTextMarginRight >= 0 ? rightTextMarginRight : (mWidth / 32);//增加右字右间距
602 | }
603 |
604 | // 计算了描绘字体需要的范围
605 | mPaint.getTextBounds(rightText, 0, rightText.length(), mTextBound);
606 | canvas.drawText(rightText, w - mTextBound.width(), baseline, mPaint);
607 |
608 | //有右侧文字,更新右边区域边界
609 | rightBound = Math.min(rightBound , w - mTextBound.width());
610 | }
611 |
612 | //处理分隔线部分
613 | if(isShowDivideLine){
614 | int left = divide_line_left_margin;
615 | int right = mWidth - divide_line_right_margin;
616 | //绘制分割线时,高度默认为 1px
617 | if(divide_line_height <= 0){
618 | divide_line_height = 1;
619 | }
620 | if(divide_line_top_gravity){
621 | int top = 0;
622 | int bottom = divide_line_height;
623 | canvas.drawRect(left, top, right, bottom, dividePaint);
624 | }else {
625 | int top = mHeight - divide_line_height;
626 | int bottom = mHeight;
627 | canvas.drawRect(left, top, right, bottom, dividePaint);
628 | }
629 | }
630 |
631 | //判断绘制 Switch
632 | if(rightViewType == RightViewType.SWITCH && isShowRightView){
633 | //边框宽度
634 | switchBackgroundPaint.setStrokeWidth(switchBorderWidth);
635 | switchBackgroundPaint.setStyle(Paint.Style.FILL);
636 |
637 | //绘制关闭状态的背景
638 | switchBackgroundPaint.setColor(uncheckSwitchBackground);
639 | drawRoundRect(canvas,
640 | switchBackgroundLeft, switchBackgroundTop, switchBackgroundRight, switchBackgroundBottom,
641 | viewRadius, switchBackgroundPaint);
642 | //绘制关闭状态的边框
643 | switchBackgroundPaint.setStyle(Paint.Style.STROKE);
644 | switchBackgroundPaint.setColor(uncheckColor);
645 | drawRoundRect(canvas,
646 | switchBackgroundLeft, switchBackgroundTop, switchBackgroundRight, switchBackgroundBottom,
647 | viewRadius, switchBackgroundPaint);
648 |
649 | //绘制未选中时的指示器小圆圈
650 | if(showSwitchIndicator){
651 | drawUncheckIndicator(canvas);
652 | }
653 |
654 | //绘制开启时的背景色
655 | float des = switchCurrentViewState.radius * .5f;//[0-backgroundRadius*0.5f]
656 | switchBackgroundPaint.setStyle(Paint.Style.STROKE);
657 | switchBackgroundPaint.setColor(switchCurrentViewState.checkStateColor);
658 | switchBackgroundPaint.setStrokeWidth(switchBorderWidth + des * 2f);
659 | drawRoundRect(canvas,
660 | switchBackgroundLeft+ des, switchBackgroundTop + des, switchBackgroundRight - des, switchBackgroundBottom - des,
661 | viewRadius, switchBackgroundPaint);
662 |
663 | //绘制按钮左边的长条遮挡
664 | switchBackgroundPaint.setStyle(Paint.Style.FILL);
665 | switchBackgroundPaint.setStrokeWidth(1);
666 | drawArc(canvas,
667 | switchBackgroundLeft, switchBackgroundTop,
668 | switchBackgroundLeft+ 2 * viewRadius, switchBackgroundTop + 2 * viewRadius,
669 | 90, 180, switchBackgroundPaint);
670 | canvas.drawRect(
671 | switchBackgroundLeft+ viewRadius, switchBackgroundTop,
672 | switchCurrentViewState.buttonX, switchBackgroundTop + 2 * viewRadius,
673 | switchBackgroundPaint);
674 |
675 | //绘制Switch的小线条
676 | if(showSwitchIndicator){
677 | drawCheckedIndicator(canvas);
678 | }
679 |
680 | //绘制Switch的按钮
681 | drawButton(canvas, switchCurrentViewState.buttonX, centerY);
682 |
683 | //更新右侧区域的边界
684 | rightBound = Math.min(rightBound , (int)switchBackgroundLeft);
685 | }
686 |
687 | //视图绘制后,计算 左区域的边界 以及 右区域的边界
688 | leftBound += 5;
689 | if(rightBound < mWidth / 2){
690 | rightBound = mWidth /2 + 5;
691 | }
692 |
693 |
694 | }
695 |
696 | @Override
697 | public boolean onTouchEvent(MotionEvent event) {
698 | //整体点击模式,不需要判断各区域的点击
699 | if (!mSplitMode) {
700 | return super.onTouchEvent(event);
701 | }
702 | //分区域点击模式,根据不同点击区域,回调接口不同的方法
703 | switch (event.getAction()) {
704 | case MotionEvent.ACTION_DOWN:
705 | int eventX = (int) event.getX();
706 | if (eventX <= leftBound) {
707 | leftStartTouchDown = true;
708 | } else if (eventX >= rightBound ) {
709 | rightStartTouchDown = true;
710 | } else {
711 | centerStartTouchDown = true;
712 | }
713 | break;
714 | case MotionEvent.ACTION_UP:
715 | int x = (int) event.getX();
716 | if (leftStartTouchDown && x <= leftBound && optionItemClickListener != null) {
717 | optionItemClickListener.leftOnClick();
718 | } else if (rightStartTouchDown && x >= rightBound && optionItemClickListener != null) {
719 | optionItemClickListener.rightOnClick();
720 | } else if (centerStartTouchDown && optionItemClickListener != null) {
721 | optionItemClickListener.centerOnClick();
722 | }
723 | leftStartTouchDown = false;
724 | centerStartTouchDown = false;
725 | rightStartTouchDown = false;
726 | break;
727 | default:
728 | break;
729 | }
730 | /*
731 | * 当右侧View 为 Switch
732 | */
733 | if(rightViewType == RightViewType.SWITCH){
734 | int actionMasked = event.getActionMasked();
735 |
736 | switch (actionMasked){
737 | case MotionEvent.ACTION_DOWN:{
738 | //只触摸到Switch上判定为即将拖动
739 | if(event.getX() > switchBackgroundLeft&& event.getX() < switchBackgroundRight){
740 | isSwitchTouchingDown = true;
741 | touchDownTime = System.currentTimeMillis();
742 | //取消准备进入拖动状态
743 | removeCallbacks(postPendingDrag);
744 | //预设100ms进入拖动状态
745 | postDelayed(postPendingDrag, 100);
746 | }
747 | break;
748 | }
749 | case MotionEvent.ACTION_MOVE:{
750 | float eventX = event.getX();
751 | if(isSwitchPendingDragState()){
752 | //在准备进入拖动状态过程中,可以拖动按钮位置
753 | float fraction = (eventX-switchBackgroundLeft) /(switchBackgroundRight - switchBackgroundLeft);
754 | fraction = Math.max(0f, Math.min(1f, fraction));
755 | //计算拖动后的圆形按钮的中心x
756 | switchCurrentViewState.buttonX = switchButtonMinX
757 | + (switchButtonMaxX - switchButtonMinX) * fraction;
758 | }else if(isSwitchDragState()){
759 | //拖动按钮位置,同时改变对应的背景颜色
760 | //计算出滑动的比例?
761 | float fraction = (eventX-switchBackgroundLeft) / (switchBackgroundRight - switchBackgroundLeft);
762 | fraction = Math.max(0f, Math.min(1f, fraction));
763 |
764 | switchCurrentViewState.buttonX = switchButtonMinX
765 | + (switchButtonMaxX - switchButtonMinX) * fraction;
766 |
767 | switchCurrentViewState.checkStateColor = (int) argbEvaluator.evaluate(
768 | fraction,
769 | uncheckColor,
770 | switchCheckedColor
771 | );
772 | postInvalidate();
773 |
774 | }
775 | break;
776 | }
777 | case MotionEvent.ACTION_UP:{
778 | isSwitchTouchingDown = false;
779 | //取消准备进入拖动状态
780 | removeCallbacks(postPendingDrag);
781 |
782 | if(System.currentTimeMillis() - touchDownTime <= 350){
783 | //点击时间小于300ms,认为是点击操作
784 | toggle();
785 | }else if(isSwitchPendingDragState()){
786 | //在准备进入拖动状态过程中就抬起了手指,Switch复位
787 | pendingCancelDragState();
788 | }else if(isSwitchDragState()){
789 | //正在拖动状态抬起了手指,计算按钮位置,设置是否切换状态
790 | float eventX = event.getX();
791 | float fraction = (eventX-switchBackgroundLeft) / (switchBackgroundRight - switchBackgroundLeft);
792 | fraction = Math.max(0f, Math.min(1f, fraction));
793 | //是否滑动过了一半
794 | boolean newCheck = fraction > 0.5f;
795 |
796 | if(newCheck == isChecked()){
797 | pendingCancelDragState();
798 | }else{
799 |
800 | pendingSettleState(newCheck);
801 | }
802 | }
803 | break;
804 | }
805 | case MotionEvent.ACTION_CANCEL:{
806 | isSwitchTouchingDown = false;
807 |
808 | removeCallbacks(postPendingDrag);
809 |
810 | if(isSwitchPendingDragState() || isSwitchDragState()){
811 | //复位
812 | pendingCancelDragState();
813 | }
814 | break;
815 | }
816 | }
817 | }
818 | return true;
819 | }
820 |
821 | //***********************属性的getter和setter方法*************************************//
822 | public String getTitleText(){
823 | return title;
824 | }
825 |
826 | public void setTitleText(String text) {
827 | title = text;
828 | invalidate();
829 | }
830 |
831 | public void setTitleText(int stringId) {
832 | title = mContext.getString(stringId);
833 | invalidate();
834 | }
835 |
836 | public void setTitleColor(int color) {
837 | titleTextColor = color;
838 | invalidate();
839 | }
840 |
841 | public void setTitleSize(int sp) {
842 | titleTextSize = sp2px(sp);
843 | invalidate();
844 | }
845 |
846 | public String getLeftText(){
847 | return leftText;
848 | }
849 |
850 | public void setLeftText(String text) {
851 | leftText = text;
852 | invalidate();
853 | }
854 |
855 | public void setLeftText(int stringId) {
856 | leftText = mContext.getString(stringId);
857 | invalidate();
858 | }
859 |
860 | public void setLeftTextColor(int color) {
861 | leftTextColor = color;
862 | invalidate();
863 | }
864 |
865 | public void setLeftImageMarginRight(int dp) {
866 | leftImageMarginRight = dp2px(dp);
867 | invalidate();
868 | }
869 |
870 | public void setLeftImageMarginLeft(int dp) {
871 | this.leftImageMarginLeft = dp2px(dp);
872 | invalidate();
873 | }
874 |
875 | public void setLeftTextMarginLeft(int dp) {
876 | this.leftTextMarginLeft = dp2px(dp);
877 | invalidate();
878 | }
879 |
880 | public void setLeftImage(Bitmap bitmap) {
881 | leftImage = bitmap;
882 | invalidate();
883 | }
884 |
885 | public void setRightImage(Bitmap bitmap) {
886 | rightImage = bitmap;
887 | invalidate();
888 | }
889 |
890 | public void setLeftImageWidthHeight(int width, int Height){
891 | this.leftImageWidth = width;
892 | this.leftImageHeight = Height;
893 | invalidate();
894 | }
895 |
896 | public void setRightViewWidthHeight(int width, int height){
897 | if(rightViewType == RightViewType.SWITCH){
898 | this.switchBackgroundWidth = width;
899 | this.switchBackgroundHeight = height;
900 | }
901 | if(rightViewType == RightViewType.IMAGE){
902 | this.rightImageWidth = width;
903 | this.rightImageHeight = height;
904 | }
905 | invalidate();
906 | }
907 |
908 | public void setLeftTextSize(int sp) {
909 | leftTextSize = sp2px(sp);
910 | invalidate();
911 | }
912 |
913 | public void setRightText(String text) {
914 | rightText = text;
915 | invalidate();
916 | }
917 |
918 | public void setRightText(int stringId) {
919 | rightText = mContext.getString(stringId);
920 | invalidate();
921 | }
922 |
923 | public void setRightTextColor(int color) {
924 | rightTextColor = color;
925 | invalidate();
926 | }
927 |
928 | public void setRightTextSize(int sp) {
929 | leftTextSize = sp2px( sp);
930 | invalidate();
931 | }
932 |
933 | public String getRightText(){
934 | return rightText;
935 | }
936 |
937 | public void setRightViewMarginLeft(int dp) {
938 | rightViewMarginLeft = dp2px(dp);
939 | invalidate();
940 | }
941 |
942 | public void setRightViewMarginRight(int dp) {
943 | this.rightViewMarginRight = dp2px(dp);
944 | invalidate();
945 | }
946 |
947 | public void setRightTextMarginRight(int dp) {
948 | this.rightTextMarginRight = dp2px(dp);
949 | invalidate();
950 | }
951 |
952 | public void showLeftImg(boolean flag) {
953 | isShowLeftImg = flag;
954 | invalidate();
955 | }
956 |
957 | public void showLeftText(boolean flag) {
958 | isShowLeftText = flag;
959 | invalidate();
960 | }
961 |
962 | public void showRightView(boolean flag) {
963 | isShowRightView = flag;
964 | invalidate();
965 | }
966 |
967 | public void showRightText(boolean flag) {
968 | isShowRightText = flag;
969 | invalidate();
970 | }
971 |
972 | public void setSplitMode(boolean splitMode) {
973 | mSplitMode = splitMode;
974 | }
975 |
976 | public boolean getSplitMode() {
977 | return mSplitMode;
978 | }
979 |
980 | public boolean getIsShowDivideLine(){
981 | return isShowDivideLine;
982 | }
983 |
984 | public void setShowDivideLine(Boolean showDivideLine) {
985 | isShowDivideLine = showDivideLine;
986 | invalidate();
987 | }
988 | public void setDivideLineColor(int color){
989 | divide_line_color = color;
990 | invalidate();
991 | }
992 |
993 | public int getRightViewType(){
994 | return rightViewType;
995 | }
996 |
997 | //Switch状态监听
998 | public void setOnSwitchCheckedChangeListener(OnSwitchCheckedChangeListener l){
999 | onSwitchCheckedChangeListener = l;
1000 | invalidate();
1001 | }
1002 |
1003 |
1004 | public void setOnOptionItemClickListener(OnOptionItemClickListener listener) {
1005 | this.optionItemClickListener = listener;
1006 | }
1007 |
1008 |
1009 |
1010 | //***********************属性的getter和setter方法*************************************//
1011 |
1012 |
1013 |
1014 |
1015 | //******************************** Switch 部分的方法***********************************//
1016 | //Switch模块的属性
1017 | private final int DEFAULT_SWITCH_BACKGROUND_WIDTH = dp2px(45);
1018 | private final int DEFAULT_SWITCH_BACKGROUND_HEIGHT = dp2px(25);
1019 | /**
1020 | * 背景位置 坐标
1021 | */
1022 | private float switchBackgroundLeft;
1023 | private float switchBackgroundTop ;
1024 | private float switchBackgroundRight ;
1025 | private float switchBackgroundBottom ;
1026 | private float centerX;
1027 | private float centerY;
1028 |
1029 | /**
1030 | * 阴影半径
1031 | */
1032 | private int switchShadowRadius;
1033 | /**
1034 | * 阴影Y偏移px
1035 | */
1036 | private int switchShadowOffset;
1037 | /**
1038 | * 阴影颜色
1039 | */
1040 | private int switchShadowColor ;
1041 |
1042 | /**
1043 | * 背景半径
1044 | */
1045 | private float viewRadius;
1046 | /**
1047 | * 按钮半径
1048 | */
1049 | private float buttonRadius;
1050 | /**
1051 | * 背景宽的值
1052 | */
1053 | private float switchBackgroundWidth;
1054 | /**
1055 | * 背景高的值
1056 | */
1057 | private float switchBackgroundHeight;
1058 |
1059 |
1060 |
1061 | /**
1062 | * 关闭后的背景底色
1063 | */
1064 | private int uncheckSwitchBackground;
1065 | /**
1066 | * 关闭后的背景颜色
1067 | */
1068 | private int uncheckColor;
1069 | /**
1070 | * 打开后的背景颜色
1071 | */
1072 | private int switchCheckedColor;
1073 | /**
1074 | * 边框宽度px
1075 | */
1076 | private int switchBorderWidth;
1077 |
1078 | /**
1079 | * 打开后的指示线颜色
1080 | */
1081 | private int checkIndicatorLineColor;
1082 | /**
1083 | * 打开后的指示线宽度(X轴)
1084 | */
1085 | private int checkIndicatorLineWidth;
1086 | /**
1087 | * 打开后的指示线的长度(Y轴)
1088 | */
1089 | private float checkLineLength;
1090 | /**
1091 | *打开后的指示线X轴的位移
1092 | */
1093 | private float switchCheckedLineOffsetX;
1094 | /**
1095 | *打开后的指示线Y轴的位移
1096 | */
1097 | private float switchCheckedLineOffsetY;
1098 |
1099 | /**
1100 | * 关闭后的圆圈的颜色
1101 | */
1102 | private int uncheckSwitchCircleColor;
1103 | /**
1104 | *关闭圆圈线宽
1105 | */
1106 | private int uncheckCircleWidth;
1107 | /**
1108 | *关闭后的圆圈的x轴位移
1109 | */
1110 | private float uncheckCircleOffsetX;
1111 | /**
1112 | *关闭后的圆圈半径
1113 | */
1114 | private float uncheckSwitchCircleRadius;
1115 |
1116 | /**
1117 | * 关闭时的圆形按钮的颜色
1118 | */
1119 | private int uncheckButtonColor;
1120 | /**
1121 | * 打开时的圆形按钮的颜色
1122 | */
1123 | private int checkedButtonColor;
1124 |
1125 |
1126 | /**
1127 | * 圆形按钮最左边坐标
1128 | */
1129 | private float switchButtonMinX;
1130 | /**
1131 | * 圆形按钮最右边的坐标
1132 | */
1133 | private float switchButtonMaxX;
1134 |
1135 | /**
1136 | * 按钮画笔
1137 | */
1138 | private Paint switchButtonPaint;
1139 | /**
1140 | * Switch的背景画笔
1141 | */
1142 | private Paint switchBackgroundPaint;
1143 |
1144 | /**
1145 | * 记录当前状态
1146 | */
1147 | private ViewState switchCurrentViewState;
1148 | private ViewState beforeState;
1149 | private ViewState afterState;
1150 |
1151 | private RectF switchBackgroundRect;
1152 |
1153 |
1154 |
1155 | /**
1156 | * 动画状态:
1157 | * 0.静止
1158 | * 1.进入拖动
1159 | * 2.处于拖动
1160 | * 3.拖动-复位
1161 | * 4.拖动-切换
1162 | * 5.点击切换
1163 | */
1164 | private final int ANIMATE_STATE_NONE = 0;
1165 | private final int ANIMATE_STATE_PENDING_DRAG = 1;
1166 | private final int ANIMATE_STATE_DRAGING = 2;
1167 | private final int ANIMATE_STATE_PENDING_RESET = 3;
1168 | private final int ANIMATE_STATE_PENDING_SETTLE = 4;
1169 | private final int ANIMATE_STATE_SWITCH = 5;
1170 |
1171 | /**
1172 | * 动画状态
1173 | */
1174 | private int animateState = ANIMATE_STATE_NONE;
1175 | /**
1176 | * 动画执行器
1177 | */
1178 | private ValueAnimator switchValueAnimator;
1179 |
1180 | private final android.animation.ArgbEvaluator argbEvaluator
1181 | = new android.animation.ArgbEvaluator();
1182 |
1183 | /**
1184 | *是否选中
1185 | */
1186 | private boolean isSwitchChecked;
1187 | /**
1188 | * 是否启用动画
1189 | */
1190 | private boolean enableSwitchAnimate;
1191 | /**
1192 | * 是否启用阴影效果
1193 | */
1194 | private boolean switchShadowEffect;
1195 | /**
1196 | * 是否显示指示器
1197 | */
1198 | private boolean showSwitchIndicator;
1199 | /**
1200 | * 是否按下
1201 | */
1202 | private boolean isSwitchTouchingDown = false;
1203 | /**
1204 | *
1205 | */
1206 | private boolean isSwitchInit = false;
1207 | /**
1208 | * Switch是否可以进行状态回调
1209 | */
1210 | private boolean isSwitchStateChangeCallback = false;
1211 |
1212 | /**
1213 | * 回调接口
1214 | */
1215 | private OnSwitchCheckedChangeListener onSwitchCheckedChangeListener;
1216 |
1217 |
1218 | /**
1219 | * 手势按下的时刻
1220 | */
1221 | private long touchDownTime;
1222 |
1223 | private Runnable postPendingDrag = new Runnable() {
1224 | @Override
1225 | public void run() {
1226 | if(!isSwitchInAnimating()){
1227 | pendingDragState();
1228 | }
1229 | }
1230 | };
1231 |
1232 | /**
1233 | * Switch UI的变化核心,更新UI的状态
1234 | */
1235 | private ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
1236 | @Override
1237 | public void onAnimationUpdate(ValueAnimator animation) {
1238 |
1239 | float value = (Float) animation.getAnimatedValue();
1240 |
1241 | switch (animateState) {
1242 | //拖动停止后 --属性动画
1243 | case ANIMATE_STATE_PENDING_SETTLE:
1244 | //拖动重置恢复 --属性动画
1245 | case ANIMATE_STATE_PENDING_RESET: {
1246 | //中间的check线颜色变化
1247 | switchCurrentViewState.checkedLineColor = (int) argbEvaluator.evaluate(
1248 | value,
1249 | beforeState.checkedLineColor,
1250 | afterState.checkedLineColor
1251 | );
1252 |
1253 | switchCurrentViewState.radius = beforeState.radius
1254 | + (afterState.radius - beforeState.radius) * value;
1255 |
1256 | if(animateState != ANIMATE_STATE_PENDING_DRAG){
1257 | switchCurrentViewState.buttonX = beforeState.buttonX
1258 | + (afterState.buttonX - beforeState.buttonX) * value;
1259 | }
1260 | //已选状态的颜色
1261 | switchCurrentViewState.checkStateColor = (int) argbEvaluator.evaluate(
1262 | value,
1263 | beforeState.checkStateColor,
1264 | afterState.checkStateColor
1265 | );
1266 | break;
1267 | }
1268 | case ANIMATE_STATE_SWITCH: {
1269 | switchCurrentViewState.buttonX = beforeState.buttonX
1270 | + (afterState.buttonX - beforeState.buttonX) * value;
1271 |
1272 | float fraction = (switchCurrentViewState.buttonX - switchButtonMinX) / (switchButtonMaxX - switchButtonMinX);
1273 |
1274 | switchCurrentViewState.checkStateColor = (int) argbEvaluator.evaluate(
1275 | fraction,
1276 | uncheckColor,
1277 | switchCheckedColor
1278 | );
1279 |
1280 | switchCurrentViewState.radius = fraction * viewRadius;
1281 | switchCurrentViewState.checkedLineColor = (int) argbEvaluator.evaluate(
1282 | fraction,
1283 | Color.TRANSPARENT,
1284 | checkIndicatorLineColor
1285 | );
1286 | break;
1287 | }
1288 | case ANIMATE_STATE_DRAGING:{
1289 | Log.i(TAG, "onAnimationUpdate: ANIMATE_STATE_DRAGING");
1290 | break;
1291 | }
1292 | case ANIMATE_STATE_NONE: {
1293 | Log.i(TAG, "onAnimationUpdate: ANIMATE_STATE_NONE ");
1294 | break;
1295 | }
1296 | default:
1297 | break;
1298 | }
1299 | postInvalidate();
1300 | }
1301 | };
1302 |
1303 | private Animator.AnimatorListener switchAnimatorListener
1304 | = new Animator.AnimatorListener() {
1305 | @Override
1306 | public void onAnimationStart(Animator animation) {
1307 | }
1308 |
1309 | @Override
1310 | public void onAnimationEnd(Animator animation) {
1311 | switch (animateState) {
1312 | case ANIMATE_STATE_PENDING_DRAG: {
1313 | animateState = ANIMATE_STATE_DRAGING;
1314 | switchCurrentViewState.checkedLineColor = Color.TRANSPARENT;
1315 | switchCurrentViewState.radius = viewRadius;
1316 | postInvalidate();
1317 | break;
1318 | }
1319 | case ANIMATE_STATE_PENDING_RESET: {
1320 | animateState = ANIMATE_STATE_NONE;
1321 | postInvalidate();
1322 | break;
1323 | }
1324 | case ANIMATE_STATE_PENDING_SETTLE: {
1325 | animateState = ANIMATE_STATE_NONE;
1326 | postInvalidate();
1327 | broadcastEvent();
1328 | break;
1329 | }
1330 | case ANIMATE_STATE_SWITCH: {
1331 | isSwitchChecked = !isSwitchChecked;
1332 | animateState = ANIMATE_STATE_NONE;
1333 | postInvalidate();
1334 | broadcastEvent();
1335 | break;
1336 | }
1337 | default:
1338 | case ANIMATE_STATE_NONE: {
1339 | break;
1340 | }
1341 | }
1342 | }
1343 |
1344 | @Override
1345 | public void onAnimationCancel(Animator animation) {
1346 | }
1347 |
1348 | @Override
1349 | public void onAnimationRepeat(Animator animation) {
1350 | }
1351 | };
1352 |
1353 | /**
1354 | * 是否在动画状态
1355 | * @return 是否在动画状态
1356 | */
1357 | private boolean isSwitchInAnimating(){
1358 | return animateState != ANIMATE_STATE_NONE;
1359 | }
1360 |
1361 | /**
1362 | * 是否在进入拖动或离开拖动状态
1363 | * @return 是否在进入拖动或离开拖动状态
1364 | */
1365 | private boolean isSwitchPendingDragState(){
1366 | return animateState == ANIMATE_STATE_PENDING_DRAG
1367 | || animateState == ANIMATE_STATE_PENDING_RESET;
1368 | }
1369 |
1370 | /**
1371 | * 是否在手指拖动状态
1372 | * @return 是否在手指拖动状态
1373 | */
1374 | private boolean isSwitchDragState(){
1375 | return animateState == ANIMATE_STATE_DRAGING;
1376 | }
1377 |
1378 | /**
1379 | * 设置是否启用阴影效果
1380 | * @param switchShadowEffect true.启用
1381 | */
1382 | public void setShadowEffect(boolean switchShadowEffect) {
1383 | if(this.switchShadowEffect == switchShadowEffect){return;}
1384 | this.switchShadowEffect = switchShadowEffect;
1385 |
1386 | if(this.switchShadowEffect){
1387 | switchButtonPaint.setShadowLayer(
1388 | switchShadowRadius,
1389 | 0, switchShadowOffset,
1390 | switchShadowColor);
1391 | }else{
1392 | switchButtonPaint.setShadowLayer(
1393 | 0,
1394 | 0, 0,
1395 | 0);
1396 | }
1397 | }
1398 |
1399 | public void setEnableEffect(boolean enable){
1400 | this.enableSwitchAnimate = enable;
1401 | }
1402 |
1403 | /**
1404 | * 开始进入拖动状态
1405 | */
1406 | private void pendingDragState() {
1407 | if(isSwitchInAnimating()){return;}
1408 | if(!isSwitchTouchingDown){return;}
1409 |
1410 | if(switchValueAnimator.isRunning()){
1411 | switchValueAnimator.cancel();
1412 | }
1413 |
1414 | animateState = ANIMATE_STATE_PENDING_DRAG;
1415 |
1416 | beforeState.copy(switchCurrentViewState);
1417 | afterState.copy(switchCurrentViewState);
1418 |
1419 | if(isChecked()){
1420 | afterState.checkStateColor = switchCheckedColor;
1421 | afterState.buttonX = switchButtonMaxX;
1422 | afterState.checkedLineColor = switchCheckedColor;
1423 | }else{
1424 | afterState.checkStateColor = uncheckColor;
1425 | afterState.buttonX = switchButtonMinX;
1426 | afterState.radius = viewRadius;
1427 | }
1428 |
1429 | switchValueAnimator.start();
1430 | }
1431 |
1432 |
1433 | /**
1434 | * 取消拖动状态
1435 | */
1436 | private void pendingCancelDragState() {
1437 | if(isSwitchDragState() || isSwitchPendingDragState()){
1438 | if(switchValueAnimator.isRunning()){
1439 | switchValueAnimator.cancel();
1440 | }
1441 |
1442 | animateState = ANIMATE_STATE_PENDING_RESET;
1443 | beforeState.copy(switchCurrentViewState);
1444 |
1445 | //开启动画,恢复到原来的状态
1446 | if(isChecked()){
1447 | setCheckedViewState(afterState);
1448 | }else{
1449 | setUncheckViewState(afterState);
1450 | }
1451 | switchValueAnimator.start();
1452 | }
1453 | }
1454 |
1455 |
1456 | /**
1457 | * 准备切换到新的状态
1458 | */
1459 | private void pendingSettleState(boolean newCheck) {
1460 | if(switchValueAnimator.isRunning()){
1461 | switchValueAnimator.cancel();
1462 | }
1463 |
1464 | animateState = ANIMATE_STATE_PENDING_SETTLE;
1465 | beforeState.copy(switchCurrentViewState);
1466 | isSwitchChecked = newCheck;
1467 | if(isChecked()){
1468 | setCheckedViewState(afterState);
1469 | }else{
1470 | setUncheckViewState(afterState);
1471 | }
1472 | switchValueAnimator.start();
1473 |
1474 | }
1475 |
1476 |
1477 | /**
1478 | * 切换到指定状态
1479 | * @param checked 状态
1480 | */
1481 | @Override
1482 | public void setChecked(boolean checked) {
1483 | if(rightViewType == RightViewType.IMAGE){
1484 | return;
1485 | }
1486 | //与当前状态相同则直接刷新
1487 | if(checked == isChecked()){
1488 | postInvalidate();
1489 | return;
1490 | }
1491 | //Switch,需要切换状态
1492 | if(rightViewType == RightViewType.SWITCH){
1493 | toggle(enableSwitchAnimate, false);
1494 | }
1495 | }
1496 |
1497 |
1498 | @Override
1499 | public boolean isChecked() {
1500 | if(rightViewType == RightViewType.SWITCH){
1501 | return isSwitchChecked;
1502 | }
1503 | return false;
1504 | }
1505 |
1506 | @Override
1507 | public void toggle() {
1508 | if(rightViewType == RightViewType.SWITCH){
1509 | toggle(true);
1510 | }
1511 | }
1512 |
1513 | /**
1514 | * 切换到对立的状态
1515 | * @param animate 是否带动画
1516 | */
1517 | public void toggle(boolean animate) {
1518 | toggle(animate, true);
1519 | }
1520 |
1521 | private void toggle(boolean animate, boolean broadcast) {
1522 | if(rightViewType != RightViewType.SWITCH){return;}
1523 |
1524 | if(!isEnabled()){return;}
1525 |
1526 | if(isSwitchStateChangeCallback){
1527 | throw new RuntimeException("You should not switch state in [onCheckedChanged]");
1528 | }
1529 | //未初始 Switch UI
1530 | if(!isSwitchInit){
1531 | isSwitchChecked = ! isSwitchChecked;
1532 | if(broadcast){
1533 | broadcastEvent();
1534 | }
1535 | return;
1536 | }
1537 |
1538 | if(switchValueAnimator.isRunning()){
1539 | switchValueAnimator.cancel();
1540 | }
1541 |
1542 | if(!enableSwitchAnimate || !animate){
1543 | isSwitchChecked = !isSwitchChecked;
1544 | if(isChecked()){
1545 | setCheckedViewState(switchCurrentViewState);
1546 | }else{
1547 | setUncheckViewState(switchCurrentViewState);
1548 | }
1549 | postInvalidate();
1550 | if(broadcast){
1551 | broadcastEvent();
1552 | }
1553 | return;
1554 | }
1555 |
1556 | animateState = ANIMATE_STATE_SWITCH;
1557 | beforeState.copy(switchCurrentViewState);
1558 |
1559 | if(isChecked()){
1560 | //当前是 checked 则切换到 unchecked
1561 | setUncheckViewState(afterState);
1562 | }else{
1563 | setCheckedViewState(afterState);
1564 | }
1565 | switchValueAnimator.start();
1566 | }
1567 |
1568 | /**
1569 | * ※切换到 非选中状态 ,记录状态值
1570 | * @param switchCurrentViewState
1571 | */
1572 | private void setUncheckViewState(ViewState switchCurrentViewState){
1573 | switchCurrentViewState.radius = 0;
1574 | switchCurrentViewState.checkStateColor = uncheckColor;
1575 | switchCurrentViewState.checkedLineColor = Color.TRANSPARENT;
1576 | switchCurrentViewState.buttonX = switchButtonMinX;
1577 | switchButtonPaint.setColor(uncheckButtonColor);
1578 | }
1579 |
1580 | /**
1581 | * ※切换到 选中状态 ,记录状态
1582 | * @param switchCurrentViewState
1583 | */
1584 | private void setCheckedViewState(ViewState switchCurrentViewState){
1585 | switchCurrentViewState.radius = viewRadius;
1586 | switchCurrentViewState.checkStateColor = switchCheckedColor;
1587 | switchCurrentViewState.checkedLineColor = checkIndicatorLineColor;
1588 | switchCurrentViewState.buttonX = switchButtonMaxX;
1589 | switchButtonPaint.setColor(checkedButtonColor);
1590 | }
1591 |
1592 |
1593 | /**
1594 | * 回调Switch状态改变事件
1595 | */
1596 | private void broadcastEvent() {
1597 | if(onSwitchCheckedChangeListener != null){
1598 | isSwitchStateChangeCallback = true;
1599 | onSwitchCheckedChangeListener.onCheckedChanged(this, isChecked());
1600 | }
1601 | isSwitchStateChangeCallback = false;
1602 | }
1603 |
1604 |
1605 | @Override
1606 | public void setPadding(int left, int top, int right, int bottom) {
1607 | super.setPadding(0,0,0,0);
1608 | }
1609 |
1610 |
1611 | /**
1612 | * 绘制选中状态指示器
1613 | * @param canvas
1614 | */
1615 | protected void drawCheckedIndicator(Canvas canvas) {
1616 | drawCheckedIndicator(canvas,
1617 | switchCurrentViewState.checkedLineColor,
1618 | checkIndicatorLineWidth,
1619 | switchBackgroundLeft + viewRadius - switchCheckedLineOffsetX, centerY - checkLineLength,
1620 | switchBackgroundLeft + viewRadius - switchCheckedLineOffsetY, centerY + checkLineLength,
1621 | switchBackgroundPaint);
1622 | }
1623 |
1624 |
1625 | /**
1626 | * 绘制选中状态指示器
1627 | * @param canvas
1628 | * @param color
1629 | * @param lineWidth
1630 | * @param sx
1631 | * @param sy
1632 | * @param ex
1633 | * @param ey
1634 | * @param switchBackgroundPaint
1635 | */
1636 | protected void drawCheckedIndicator(Canvas canvas, int color, float lineWidth,
1637 | float sx, float sy, float ex, float ey, Paint paint) {
1638 | paint.setStyle(Paint.Style.STROKE);
1639 | paint.setColor(color);
1640 | paint.setStrokeWidth(lineWidth);
1641 | canvas.drawLine(sx, sy, ex, ey, paint);
1642 | }
1643 |
1644 |
1645 | /**
1646 | * 绘制关闭状态指示器
1647 | * @param canvas
1648 | */
1649 | private void drawUncheckIndicator(Canvas canvas) {
1650 | drawUncheckIndicator(canvas,
1651 | uncheckSwitchCircleColor,
1652 | uncheckCircleWidth,
1653 | switchBackgroundRight - uncheckCircleOffsetX, centerY,
1654 | uncheckSwitchCircleRadius,
1655 | switchBackgroundPaint);
1656 | }
1657 |
1658 | protected void drawUncheckIndicator(Canvas canvas,
1659 | int color,
1660 | float lineWidth,
1661 | float centerX, float centerY,
1662 | float radius,
1663 | Paint paint) {
1664 | paint.setStyle(Paint.Style.STROKE);
1665 | paint.setColor(color);
1666 | paint.setStrokeWidth(lineWidth);
1667 | canvas.drawCircle(centerX, centerY, radius, paint);
1668 | }
1669 |
1670 | /**
1671 | * 绘制圆弧
1672 | *
1673 | * @param canvas
1674 | * @param switchBackgroundLeft 背景左顶点坐标
1675 | * @param top
1676 | * @param right
1677 | * @param bottom
1678 | * @param startAngle
1679 | * @param sweepAngle
1680 | * @param paint
1681 | */
1682 | private void drawArc(Canvas canvas,
1683 | float switchBackgroundLeft, float top,
1684 | float right, float bottom,
1685 | float startAngle, float sweepAngle,
1686 | Paint paint){
1687 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1688 | canvas.drawArc(switchBackgroundLeft, top, right, bottom,
1689 | startAngle, sweepAngle, true, paint);
1690 | }else{
1691 | switchBackgroundRect.set(switchBackgroundLeft, top, right, bottom);
1692 | canvas.drawArc(switchBackgroundRect,
1693 | startAngle, sweepAngle, true, paint);
1694 | }
1695 | }
1696 |
1697 | /**
1698 | * 绘制带圆弧的矩形
1699 | * @param canvas
1700 | * @param switchBackgroundLeft
1701 | * @param top
1702 | * @param right
1703 | * @param bottom
1704 | * @param backgroundRadius
1705 | * @param paint
1706 | */
1707 | private void drawRoundRect(Canvas canvas,
1708 | float switchBackgroundLeft, float top,
1709 | float right, float bottom,
1710 | float backgroundRadius,
1711 | Paint paint){
1712 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1713 | canvas.drawRoundRect(switchBackgroundLeft, top, right, bottom,
1714 | backgroundRadius, backgroundRadius, paint);
1715 |
1716 | }else{
1717 | switchBackgroundRect.set(switchBackgroundLeft, top, right, bottom);
1718 | canvas.drawRoundRect(switchBackgroundRect,
1719 | backgroundRadius, backgroundRadius, paint);
1720 | }
1721 | }
1722 |
1723 |
1724 | /**
1725 | * 绘制圆形按钮
1726 | * @param canvas
1727 | * @param x px
1728 | * @param y px
1729 | */
1730 | private void drawButton(Canvas canvas, float x, float y) {
1731 | canvas.drawCircle(x, y, buttonRadius, switchButtonPaint);
1732 |
1733 | switchBackgroundPaint.setStyle(Paint.Style.STROKE);
1734 | switchBackgroundPaint.setStrokeWidth(1);
1735 | switchBackgroundPaint.setColor(0XffDDDDDD);
1736 | canvas.drawCircle(x, y, buttonRadius, switchBackgroundPaint);
1737 | }
1738 |
1739 |
1740 |
1741 | /**
1742 | * 保存Switch的状态属性
1743 | * */
1744 | private static class ViewState {
1745 | /**
1746 | * 按钮x的坐标[switchButtonMinX-switchButtonMaxX]
1747 | */
1748 | float buttonX;
1749 | /**
1750 | * 状态背景颜色
1751 | */
1752 | int checkStateColor;
1753 | /**
1754 | * 选中线的颜色
1755 | */
1756 | int checkedLineColor;
1757 | /**
1758 | * 状态背景的半径
1759 | */
1760 | float radius;
1761 | ViewState(){}
1762 | private void copy(ViewState source){
1763 | this.buttonX = source.buttonX;
1764 | this.checkStateColor = source.checkStateColor;
1765 | this.checkedLineColor = source.checkedLineColor;
1766 | this.radius = source.radius;
1767 | }
1768 | }
1769 |
1770 | //******************************** Switch 部分的方法***********************************//
1771 |
1772 |
1773 | /**
1774 | * 分区域点击 事件接口
1775 | */
1776 | public interface OnOptionItemClickListener {
1777 | void leftOnClick();
1778 |
1779 | void centerOnClick();
1780 |
1781 | void rightOnClick();
1782 | }
1783 |
1784 | /*
1785 | * Switch 状态事件改变回调接口
1786 | */
1787 | public interface OnSwitchCheckedChangeListener{
1788 | void onCheckedChanged(OptionBarView view, boolean isSwitchChecked);
1789 | }
1790 |
1791 |
1792 |
1793 |
1794 |
1795 | private static int optInt(TypedArray typedArray,
1796 | int index,
1797 | int def) {
1798 | if(typedArray == null){return def;}
1799 | return typedArray.getInt(index, def);
1800 | }
1801 |
1802 |
1803 | private static float optPixelSize(TypedArray typedArray,
1804 | int index,
1805 | float def) {
1806 | if(typedArray == null){return def;}
1807 | return typedArray.getDimension(index, def);
1808 | }
1809 |
1810 | private static int optPixelSize(TypedArray typedArray,
1811 | int index,
1812 | int def) {
1813 | if(typedArray == null){return def;}
1814 | return typedArray.getDimensionPixelOffset(index, def);
1815 | }
1816 |
1817 | private static int optColor(TypedArray typedArray,
1818 | int index,
1819 | int def) {
1820 | if(typedArray == null){return def;}
1821 | return typedArray.getColor(index, def);
1822 | }
1823 |
1824 | private static boolean optBoolean(TypedArray typedArray,
1825 | int index,
1826 | boolean def) {
1827 | if(typedArray == null){return def;}
1828 | return typedArray.getBoolean(index, def);
1829 | }
1830 |
1831 | private static int optResourceId(TypedArray typedArray,
1832 | int index,
1833 | int def){
1834 | if(typedArray == null){return def;}
1835 | return typedArray.getResourceId(index , def);
1836 | }
1837 |
1838 | private static String optString(TypedArray typedArray ,int index ,String def){
1839 | if (typedArray == null) {return def;}
1840 | //获取字符串可能为空
1841 | String s = typedArray.getString(index);
1842 | if(s != null) return s;
1843 | else return def;
1844 | }
1845 |
1846 | private int sp2px(float spVal) {
1847 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
1848 | spVal, Resources.getSystem().getDisplayMetrics());
1849 | }
1850 |
1851 | private int dp2px( float dpVal) {
1852 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
1853 | dpVal, Resources.getSystem().getDisplayMetrics());
1854 | }
1855 |
1856 | /**
1857 | * 将Vector类型的Drawable转换为Bitmap
1858 | * @param vectorDrawableId vector资源id
1859 | * @return bitmap
1860 | */
1861 | private Bitmap decodeVectorToBitmap(int vectorDrawableId ){
1862 | Drawable vectorDrawable = null;
1863 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
1864 | vectorDrawable = this.mContext.getDrawable(vectorDrawableId);
1865 | }else{
1866 | vectorDrawable = getResources().getDrawable(vectorDrawableId);
1867 | }
1868 | if(vectorDrawable != null){
1869 | //这里若使用Bitmap.Config.RGB565会导致图片资源黑底
1870 | Bitmap b = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),vectorDrawable.getMinimumHeight(), Bitmap.Config.ARGB_8888);
1871 | Canvas canvas = new Canvas(b);
1872 | vectorDrawable.setBounds(0,0,canvas.getWidth(),canvas.getHeight());
1873 | vectorDrawable.draw(canvas);
1874 | return b;
1875 | }
1876 | return null;
1877 | }
1878 |
1879 |
1880 | /**
1881 | * 右侧View的Type
1882 | */
1883 | public @interface RightViewType {
1884 |
1885 | int IMAGE = 0;
1886 |
1887 | int SWITCH = 1;
1888 |
1889 | }
1890 |
1891 |
1892 |
1893 | }
1894 |
--------------------------------------------------------------------------------
/OptionBarView/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## OptionBarView
2 |
3 | [](https://jitpack.io/#DMingOu/OptionBarView)   
4 |
5 | ## 项目简介
6 | **一个轻量级的适合快速编写出一个高度可定制的条目设置选项类型的View**
7 |
8 | ### 为什么想要封装一个这样的View?
9 |
10 | 在做项目的过程中发现经常地要写各种各样的点击选项的条目,常见的"设置页"的条目,一般的做法是每写一个条目选项就要写一个布局然后里面配置一堆的View,虽然也能完成效果,但是如果数量很多或者设计图效果各异就会容易出错浪费很多时间,同时一个页面如果有过多的布局嵌套也会影响效率。
11 |
12 | 于是,我开始找一些定制性高且内部通过纯Canvas就能完成所有绘制的框架。最后,我找到了由[GitLqr](https://github.com/GitLqr)作者开发的[LQROptionItemView](https://github.com/GitLqr/LQROptionItemView),大体满足需求,在此非常感谢作者[GitLqr](https://github.com/GitLqr),但是在使用过程中发现几个小问题:
13 |
14 | - **图片均不能设置宽度和高度**
15 | - **图片不支持直接设置Vector矢量资源**
16 | - **不支持顶部/底部绘制分割线**
17 | - **左 中 右 区域识别有误差**
18 | - **不支持右侧View为Switch这种常见情况**
19 |
20 | 由于原作者的项目近几年好像都没有继续维护了,于是我打算自己动手改进以上的问题,并开源出来。
21 |
22 | ## 主要功能
23 |
24 | - 绘制左、中、右侧的文字
25 | - 绘制左、右侧的图片
26 | - 定制右侧的Switch(IOS风格)
27 | - 设置顶部或底部的分割线
28 | - 定制View与文字的大小和距离
29 | - 识别左中右分区域的点击
30 |
31 | ## 效果演示
32 |
33 | 下图列举了几种常见的条目效果,项目还支持更多不同的效果搭配。
34 |
35 |
36 |
37 |
38 | ## Gradle集成方式
39 |
40 | [](https://jitpack.io/#DMingOu/OptionBarView)
41 |
42 | 在Project 的 build.gradle
43 |
44 | ```
45 | allprojects {
46 | repositories {
47 | ...
48 | maven { url 'https://jitpack.io' }
49 | }
50 | }
51 | ```
52 |
53 | 在Module 的 build.gradle
54 |
55 | ```
56 | dependencies {
57 | implementation 'com.github.DMingOu:OptionBarView:1.1.0'
58 | }
59 | ```
60 |
61 |
62 |
63 | ## **快速上手**
64 |
65 | ### 1、在XML布局中使用
66 |
67 | 属性均可选,不设置的属性则不显示,⭐图片与文字的距离若不设置会有一个默认的距离,可设置任意类型的图片资源。
68 |
69 | ```xml
70 |
97 | ```
98 | 或者右侧为一个Switch:
99 | ```xml
100 |
119 | ```
120 |
121 | ### 2、在Java代码里动态添加
122 |
123 | 方式与其他View相同,也是确定布局参数,通过api设置OptionBarView的属性,这里就不阐述了
124 |
125 | ### 3、条目点击事件
126 |
127 | #### 整体点击模式
128 |
129 | 默认开启的是整体点击模式,可以通过`setSplitMode(false)`手动开启
130 |
131 | ```java
132 | opv2.setOnClickListener(new View.OnClickListener() {
133 | @Override
134 | public void onClick(View view) {
135 | Toast.makeText(MainActivity.this,"OptionBarView Click",Toast.LENGTH_LONG).show();
136 | }
137 | });
138 | ```
139 |
140 | #### 分区域点击模式
141 |
142 | 默认不会开启分区域点击模式,可以通过`setSplitMode(true)`开启,通过设置接口回调进行监听事件
143 |
144 | ```java
145 | opv1.setSplitMode(true);
146 | opv1.setOnOptionItemClickListener(new OptionBarView.OnOptionItemClickListener() {
147 | @Override
148 | public void leftOnClick() {
149 | Toast.makeText(MainActivity.this,"Left Click",Toast.LENGTH_SHORT).show();
150 | }
151 | @Override
152 | public void centerOnClick() {
153 | Toast.makeText(MainActivity.this,"Center Click",Toast.LENGTH_SHORT).show();
154 | }
155 | @Override
156 | public void rightOnClick() {
157 | Toast.makeText(MainActivity.this,"Right Click",Toast.LENGTH_SHORT).show();
158 | }
159 | });
160 | ```
161 |
162 | #### 分区域点击模式下对Switch进行状态改变监听
163 | ```java
164 | opvSwitch = findViewById(R.id.opv_switch);
165 | opvSwitch.setSplitMode(true);
166 | opvSwitch.setOnSwitchCheckedChangeListener(new OptionBarView.OnSwitchCheckedChangeListener() {
167 | @Override
168 | public void onCheckedChanged(OptionBarView view, boolean isChecked) {
169 | Toast.makeText(MainActivity.this,"Switch是否被打开:"+isChecked,Toast.LENGTH_SHORT).show();
170 | }
171 | });
172 | ```
173 |
174 |
175 | ### 4、API
176 |
177 | ```java
178 | //中间标题
179 | getTitleText()
180 | setTitleText(String text)
181 | setTitleText(int stringId)
182 | setTitleColor(int color)
183 | setTitleSize(int sp)
184 |
185 | //左侧
186 | getLeftText()
187 | setLeftText(String text)
188 | setLeftText(int stringId)
189 | setLeftTextSize(int sp)
190 | setLeftTextColor(int color)
191 | setLeftTextMarginLeft(int dp)
192 | setLeftImageMarginLeft(int dp)
193 | setLeftImageMarginRight(int dp)
194 | setLeftImage(Bitmap bitmap)
195 | showLeftImg(boolean flag)
196 | showLeftText(boolean flag)
197 | setLeftImageWidthHeight(int width, int Height)
198 |
199 | //右侧
200 | getRightText()
201 | setRightImage(Bitmap bitmap)
202 | setRightText(String text)
203 | setRightText(int stringId)
204 | setRightTextColor(int color)
205 | setRightTextSize(int sp)
206 | setRightTextMarginRight(int dp)
207 | setRightViewMarginLeft(int dp)
208 | setRightViewMarginRight(int dp)
209 | showRightImg(boolean flag)
210 | showRightText(boolean flag)
211 | setRightViewWidthHeight(int width, int height)
212 | getRightViewType()
213 | showRightView(boolean flag)
214 | setChecked(boolean checked)
215 | isChecked()
216 | toggle(boolean animate)
217 |
218 |
219 | //点击模式
220 | setSplitMode(boolean splitMode)
221 | getSplitMode()
222 |
223 | //分割线
224 | getIsShowDivideLine()
225 | setShowDivideLine(Boolean showDivideLine)
226 | setDivideLineColor(int divideLineColor)
227 | ```
228 |
229 | ### 5、特殊属性说明
230 |
231 | 主要是对一些图片文字的距离属性的说明。看图就能明白了。
232 |
233 | 属性更新说明:
234 |
235 | ~~right_image_margin_left~~ 更新为 `right_view_margin_left`
236 |
237 | ~~right_image_margin_right~~ 更新为 `right_view_margin_right`
238 |
239 |
240 |
241 | ## 混淆
242 |
243 | ```
244 | -dontwarn com.dmingo.optionbarview.*
245 | -keep class com.dmingo.optionbarview.*{*;}
246 | ```
247 |
248 | ## 联系方式
249 |
250 | - Gmail: [dmingou0529@gmail.com](mailto:dmingou0529@gmail.com)
251 |
252 | - QQ Email: [758502274@qq.com](mailto:758502274@qq.com)
253 |
254 | - QQ: 758502274
255 |
256 |
257 | ## License
258 |
259 | ```
260 | MIT License
261 |
262 | Copyright (c) 2020 DMingOu
263 |
264 | Permission is hereby granted, free of charge, to any person obtaining a copy
265 | of this software and associated documentation files (the "Software"), to deal
266 | in the Software without restriction, including without limitation the rights
267 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
268 | copies of the Software, and to permit persons to whom the Software is
269 | furnished to do so, subject to the following conditions:
270 |
271 | The above copyright notice and this permission notice shall be included in all
272 | copies or substantial portions of the Software.
273 |
274 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
275 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
276 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
277 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
278 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
279 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
280 | SOFTWARE.
281 | ```
282 |
283 |
284 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 30
5 |
6 | defaultConfig {
7 | applicationId "com.dmingo"
8 | minSdkVersion 14
9 | targetSdkVersion 30
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | implementation fileTree(dir: "libs", include: ["*.jar"])
26 | implementation 'androidx.appcompat:appcompat:1.2.0'
27 | testImplementation 'junit:junit:4.13'
28 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
29 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
30 |
31 | // implementation 'com.github.DMingOu:OptionBarView:1.0.0'
32 | //引入模块
33 | implementation project(':OptionBarView')
34 |
35 | }
--------------------------------------------------------------------------------
/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 |
23 | -dontwarn com.dmingo.optionbarview.*
24 | -keep class com.dmingo.optionbarview.*{*;}
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dmingo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.dmingo;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 | assertEquals("com.dmingo", appContext.getPackageName());
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dmingo/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dmingo.sample;
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.AdapterView;
7 | import android.widget.Toast;
8 |
9 | import com.dmingo.R;
10 | import com.dmingo.optionbarview.OptionBarView;
11 |
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | OptionBarView opv1;
16 | OptionBarView opv2;
17 |
18 | OptionBarView opvSwitch;
19 |
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | opv1 =findViewById(R.id.opv_1);
26 | opv1.setSplitMode(true);
27 | opv1.setOnOptionItemClickListener(new OptionBarView.OnOptionItemClickListener() {
28 | @Override
29 | public void leftOnClick() {
30 | Toast.makeText(MainActivity.this,"Left Click",Toast.LENGTH_SHORT).show();
31 | }
32 | @Override
33 | public void centerOnClick() {
34 | Toast.makeText(MainActivity.this,"Center Click",Toast.LENGTH_SHORT).show();
35 | }
36 | @Override
37 | public void rightOnClick() {
38 | Toast.makeText(MainActivity.this,"Right Click",Toast.LENGTH_SHORT).show();
39 | }
40 | });
41 |
42 | opv2 = findViewById(R.id.opv_2);
43 | opv2.setOnClickListener(new View.OnClickListener() {
44 | @Override
45 | public void onClick(View view) {
46 | Toast.makeText(MainActivity.this,"OptionBarView Click",Toast.LENGTH_LONG).show();
47 | }
48 | });
49 | opv2.setRightText("hadhadasdas");
50 |
51 | opvSwitch = findViewById(R.id.opv_switch);
52 | opvSwitch.setSplitMode(true);
53 | opvSwitch.setOnSwitchCheckedChangeListener(new OptionBarView.OnSwitchCheckedChangeListener() {
54 | @Override
55 | public void onCheckedChanged(OptionBarView view, boolean isChecked) {
56 | Toast.makeText(MainActivity.this,"Switch是否被打开:"+isChecked,Toast.LENGTH_SHORT).show();
57 | }
58 | });
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_vector.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
38 |
39 |
56 |
57 |
58 |
81 |
82 |
110 |
111 |
145 |
146 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | OptionBar
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/dmingo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dmingo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath "com.android.tools.build:gradle:4.0.1"
9 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | maven { url 'https://jitpack.io' }
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DMingOu/OptionBarView/dc9c59b88ce0525897abbff7c0b6dd8495812328/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 03 21:53:26 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':OptionBarView'
2 | include ':app'
3 | rootProject.name = "OptionBar"
--------------------------------------------------------------------------------