├── .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/v/DMingOu/OptionBarView.svg)](https://jitpack.io/#DMingOu/OptionBarView) ![](http://picbed-dmingou.oss-cn-shenzhen.aliyuncs.com/img/platform-Android-lightgrey.svg) ![](http://picbed-dmingou.oss-cn-shenzhen.aliyuncs.com/img/license-MIT-000000.svg) ![](https://img.shields.io/badge/author-DMingOu-blue.svg) 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/v/DMingOu/OptionBarView.svg)](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" --------------------------------------------------------------------------------