diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 7a1d625..90e1a2d 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 7ac24c7..7377cc1 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -9,6 +9,7 @@ - + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 2f210fe..5186132 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,16 @@ # StepViewAndroid An Android library written in kotlin to display steps (without any max-min limits) along with the status/description. It also supports some really cool features. +## Custom Views Tutorial +For any information on how to create custom views you can refer to the link below.
+https://medium.com/mindorks/how-to-create-custom-views-141dc0570e57 + +## Setup + +``` +implementation 'com.params.stepview:stepview:1.0.2' +``` + ## Usage ### A customised StepView @@ -18,7 +28,7 @@ An Android library written in kotlin to display steps (without any max-min limit * Stroke color (circle), fill color (circle), line color & label text color can be set differently for completed, current & incomplete states. ```XML - - - diff --git a/app/src/main/java/params/com/statusView/StatusView.kt b/stepview/src/main/java/params/com/stepview/StatusView.kt similarity index 77% rename from app/src/main/java/params/com/statusView/StatusView.kt rename to stepview/src/main/java/params/com/stepview/StatusView.kt index e16e9d4..c684c2e 100644 --- a/app/src/main/java/params/com/statusView/StatusView.kt +++ b/stepview/src/main/java/params/com/stepview/StatusView.kt @@ -1,4 +1,4 @@ -package params.com.statusView +package params.com.stepview import android.content.Context import android.graphics.* @@ -20,17 +20,18 @@ class StatusViewScroller @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : HorizontalScrollView(context, attrs, defStyleAttr) { - private var statusView:StatusView = StatusView(context,attrs) + var statusView: StatusView = StatusView(context, attrs) - init{ + init { addView(statusView) } - fun scrollToStep(stepCount:Int){ - scrollTo(statusView.getScrollPosForStep(stepCount),scrollY) + fun scrollToStep(stepCount: Int) { + scrollTo(statusView.getScrollPosForStep(stepCount), scrollY) } - fun smoothScrollToStep(stepCount:Int){ - smoothScrollTo(statusView.getScrollPosForStep(stepCount),scrollY) + + fun smoothScrollToStep(stepCount: Int) { + smoothScrollTo(statusView.getScrollPosForStep(stepCount), scrollY) } } @@ -42,7 +43,6 @@ class StatusView @JvmOverloads constructor( ) : View(context, attrs, defStyleAttr) { - companion object { const val CIRCLE_COLOR_TYPE_FILL = 1 const val CIRCLE_COLOR_TYPE_STROKE = 2 @@ -73,12 +73,12 @@ class StatusView @JvmOverloads constructor( * Magnification value for current Circle */ @setparam:FloatRange(from = 0.0, to = 1.0) - var currentStatusZoom:Float = 0.0f - set(value) { - require(value in 0..1) { "Zoom should be in between 0 to 1, but was $value." } + var currentStatusZoom: Float = 0.0f + set(value) { + require(value in 0..1) { "Zoom should be in between 0 to 1, but was $value." } - currentStatusRadius = circleRadius * (1 + value) - } + currentStatusRadius = circleRadius * (1 + value) + } /** * A min margin that there should be between every adjacent status Text @@ -98,14 +98,16 @@ class StatusView @JvmOverloads constructor( /** - * The total number of statuses to be displayed - */ + * The total number of statuses to be displayed + */ var stepCount: Int by OnLayoutProp(4) /* The count up to which status has been completed */ - var currentCount: Int by OnLayoutProp(INVALID_STATUS_COUNT) + var currentCount: Int by OnLayoutProp(INVALID_STATUS_COUNT){ + initCirclePaints() + } /* @@ -120,31 +122,31 @@ class StatusView @JvmOverloads constructor( * Length of line to be drawn between circles * #Note: This does not include line gap */ - var lineLength: Float by OnLayoutProp(30.0f.pxValue()) //dp + var lineLength: Float by OnLayoutProp(30.0f.pxValue()) //dp /** * Stroke width of each circle to be drawn */ - var circleStrokeWidth: Float by OnLayoutProp(2.0f.pxValue()) //dp + var circleStrokeWidth: Float by OnLayoutProp(2.0f.pxValue()) //dp /** * Margin or gap on each side of circle. * Note: This does not apply for extreme sides. */ - var lineGap by OnLayoutProp( 0.0f) + var lineGap by OnLayoutProp(0.0f) /** * Top Margin of Status from circle */ - var statusTopMargin by OnLayoutProp( 4.0f.pxValue()) + var statusTopMargin by OnLayoutProp(4.0f.pxValue()) /** * set true to align all status to align same as current Status y-pos * */ - var alignStatusWithCurrent by OnValidateProp(false){ + var alignStatusWithCurrent by OnValidateProp(false) { setDrawingDimensions() } @@ -160,91 +162,92 @@ class StatusView @JvmOverloads constructor( /** * Color of line between circles */ - var lineColor: Int by OnValidateProp(Color.BLACK){ + var lineColor: Int by OnValidateProp(Color.BLACK) { mLinePaint.color = lineColor } /** * Color of line between circles for incomplete statuses */ - var lineColorIncomplete: Int by OnValidateProp(Color.BLACK){ + var lineColorIncomplete: Int by OnValidateProp(Color.BLACK) { mLinePaintIncomplete.color = lineColorIncomplete } /** * Color of line adjacent to circles for current status */ - var lineColorCurrent: Int by OnValidateProp(Color.BLACK){ + var lineColorCurrent: Int by OnValidateProp(Color.BLACK) { mLinePaintCurrent.color = lineColorCurrent } /** * Fill Color of circles for complete statuses */ - var circleFillColor: Int by OnValidateProp(Color.CYAN){ + var circleFillColor: Int by OnValidateProp(Color.CYAN) { mCircleFillPaint?.color = circleFillColor } /** * Fill Color of circles for incomplete statuses */ - var circleFillColorIncomplete: Int by OnValidateProp(Color.CYAN){ + var circleFillColorIncomplete: Int by OnValidateProp(Color.CYAN) { mCircleFillPaintIncomplete?.color = circleFillColorIncomplete } /** * Fill Color of circles for current status */ - var circleFillColorCurrent: Int by OnValidateProp(Color.CYAN){ + var circleFillColorCurrent: Int by OnValidateProp(Color.CYAN) { mCircleFillPaintCurrent?.color = circleFillColorCurrent } /** * Stroke Color of circles for complete statuses */ - var circleStrokeColor: Int by OnValidateProp(Color.BLACK){ + var circleStrokeColor: Int by OnValidateProp(Color.BLACK) { mCircleStrokePaint?.color = circleStrokeColor } /** * Stroke Color of circles for incomplete statuses */ - var circleStrokeColorIncomplete: Int by OnValidateProp(Color.BLACK){ + var circleStrokeColorIncomplete: Int by OnValidateProp(Color.BLACK) { mCircleStrokePaintIncomplete?.color = circleStrokeColorIncomplete } /** * Stroke Color of circles for current Status */ - var circleStrokeColorCurrent: Int by OnValidateProp(Color.BLACK){ + var circleStrokeColorCurrent: Int by OnValidateProp(Color.BLACK) { mCircleStrokePaintCurrent?.color = circleStrokeColorCurrent } /** * Text Color of labels * */ - var textColorLabels: Int by OnValidateProp(Color.BLACK){ + var textColorLabels: Int by OnValidateProp(Color.BLACK) { mTextPaintLabels.color = textColorLabels } /** * Text Color of labels Incomplete */ - var textColorLabelsIncomplete: Int by OnValidateProp(Color.BLACK){ + var textColorLabelsIncomplete: Int by OnValidateProp(Color.BLACK) { mTextPaintLabelsIncomplete.color = textColorLabelsIncomplete } /** * Text Color of labels Current */ - var textColorLabelCurrent : Int by OnValidateProp(Color.BLACK){ + var textColorLabelCurrent: Int by OnValidateProp(Color.BLACK) { mTextPaintLabelCurrent.color = textColorLabelCurrent } /** * Text Size of Labels */ - var textSizeLabels: Float by OnValidateProp(15.0f.pxValue(TypedValue.COMPLEX_UNIT_SP) ){//sp + var textSizeLabels: Float by OnValidateProp(15.0f.pxValue(TypedValue.COMPLEX_UNIT_SP)) { + //sp mTextPaintLabels.textSize = textSizeLabels mTextPaintLabelsIncomplete.textSize = textSizeLabels @@ -253,9 +256,9 @@ class StatusView @JvmOverloads constructor( /** * Text Color of Statuses */ - var textColorStatus: Int by OnValidateProp(Color.BLACK){ + var textColorStatus: Int by OnValidateProp(Color.BLACK) { mTextPaintStatus.color = textColorStatus - for(item in statusData){ + for (item in statusData) { item.staticLayout?.paint?.color = textColorStatus } } @@ -264,7 +267,7 @@ class StatusView @JvmOverloads constructor( * Text Size of statuses */ - var textSizeStatus: Float by OnLayoutProp(14.0f.pxValue(TypedValue.COMPLEX_UNIT_SP)){ + var textSizeStatus: Float by OnLayoutProp(14.0f.pxValue(TypedValue.COMPLEX_UNIT_SP)) { mTextPaintStatus.textSize = textSizeStatus } @@ -272,7 +275,7 @@ class StatusView @JvmOverloads constructor( * Text Font of statuses */ - var statusTypeface:Typeface? by OnLayoutProp(null){ + var statusTypeface: Typeface? by OnLayoutProp(null) { statusTypeface?.run { mTextPaintStatus.typeface = this } @@ -283,7 +286,7 @@ class StatusView @JvmOverloads constructor( * Text Font of Labels */ - var labelsTypeface:Typeface? by OnLayoutProp(null){ + var labelsTypeface: Typeface? by OnLayoutProp(null) { labelsTypeface?.run { mTextPaintLabels.typeface = this mTextPaintLabelsIncomplete.typeface = this @@ -296,7 +299,7 @@ class StatusView @JvmOverloads constructor( * A boolean which decides if to draw labels or not */ - var drawLabels: Boolean by OnValidateProp(false){ + var drawLabels: Boolean by OnValidateProp(false) { setDrawingDimensions() } @@ -304,7 +307,7 @@ class StatusView @JvmOverloads constructor( * A drawable for complete status * #Note: If this is set then it would be given preference over the labels. */ - var completeDrawable: Drawable? by OnValidateProp(null){ + var completeDrawable: Drawable? by OnValidateProp(null) { setDrawingDimensions() } @@ -312,7 +315,7 @@ class StatusView @JvmOverloads constructor( * A drawable for current Status * #Note: If this is set then it would be given preference over the labels. */ - var currentDrawable: Drawable? by OnValidateProp(null){ + var currentDrawable: Drawable? by OnValidateProp(null) { setDrawingDimensions() } @@ -320,7 +323,7 @@ class StatusView @JvmOverloads constructor( * A drawable for incomplete statuses * #Note: If this is set then it would be given preference over the labels. */ - var incompleteDrawable: Drawable? by OnValidateProp(null){ + var incompleteDrawable: Drawable? by OnValidateProp(null) { setDrawingDimensions() } @@ -331,20 +334,19 @@ class StatusView @JvmOverloads constructor( * To set both the flags i.e (fill & stroke) pass (CIRCLE_COLOR_TYPE_FILL | CIRCLE_COLOR_TYPE_STROKE) * */ - var circleColorType:Int by Delegates.observable(CIRCLE_COLOR_TYPE_FILL) + var circleColorType: Int by Delegates.observable(CIRCLE_COLOR_TYPE_FILL) { prop, old, new -> - if(ViewCompat.isLaidOut(this)){ + if (ViewCompat.isLaidOut(this)) { initCirclePaints() val oldHadStrokeFlagSet = containsFlag(old, CIRCLE_COLOR_TYPE_STROKE) val newHasStrokeFlagSet = containsFlag(new, CIRCLE_COLOR_TYPE_STROKE) - if((oldHadStrokeFlagSet && !newHasStrokeFlagSet) || (!oldHadStrokeFlagSet && newHasStrokeFlagSet)) - { + if ((oldHadStrokeFlagSet && !newHasStrokeFlagSet) || (!oldHadStrokeFlagSet && newHasStrokeFlagSet)) { requestLayout() - }else{ + } else { setDrawingDimensions() invalidate() } @@ -357,12 +359,12 @@ class StatusView @JvmOverloads constructor( * To the caller. It is a list of string and custom getter setters have been made accordingly * */ - private var statusData:MutableList by OnLayoutProp(mutableListOf()) + private var statusData: MutableList by OnLayoutProp(mutableListOf()) /** * Extracts List from List and returns back to the caller */ - fun getStatusList():List = statusData.map { it.text } + fun getStatusList(): List = statusData.map { it.text } /** * Creates List from List by passing default values. @@ -370,9 +372,13 @@ class StatusView @JvmOverloads constructor( */ fun setStatusList(list: List) { //to make sure original list is not modified convert to mutableList - val input = list.toMutableList().dropLast(stepCount) + val input = list.toMutableList() + if (stepCount > 0 && list.size - stepCount > 0) { + input.dropLast(list.size - stepCount) + } - statusData = (input.map{StatusInfo(it)}).toMutableList() + + statusData = (input.map { StatusInfo(it) }).toMutableList() } @@ -382,14 +388,12 @@ class StatusView @JvmOverloads constructor( * Stores all the drawing data that is used while drawing on canvas */ private var drawingData = mutableListOf() - private var currentStatusRadius:Float by OnLayoutProp(circleRadius) + private var currentStatusRadius: Float by OnLayoutProp(circleRadius) private var lineLengthComputed = 0.0f //actual linelength that is calculated and set - - //To store the data of each circle - private class Item(val textData: LabelItemText?, val circleItem: CircleItem, val lineItem: LineItem?, val labelData: StatusItemText?=null) + private class Item(val textData: LabelItemText?, val circleItem: CircleItem, val lineItem: LineItem?, val labelData: StatusItemText? = null) //Stores drawing data about labels to be drawn inside circles i.e the count of step private class LabelItemText(val text: String? = null, val paint: Paint? = null, val x: Float = 0.0f, val y: Float = 0.0f, val drawableItem: DrawableItem? = null) @@ -407,13 +411,13 @@ class StatusView @JvmOverloads constructor( private class DrawableItem(val rect: Rect, val drawable: Drawable) //Stores information about every status text and its dimension properties - private class StatusInfo(val text:String, var width:Float=0.0f, var height:Float=0.0f, var staticLayout: StaticLayout? = null) - + private class StatusInfo(val text: String, var width: Float = 0.0f, var height: Float = 0.0f, var staticLayout: StaticLayout? = null) + private var propsIntialisedOnce = false init { - id = ViewCompat.generateViewId()//as the same attrs are being passed this view would have the same id as scroller. + id = ViewCompat.generateViewId()//as the same attrs are being passed this view would have the same id as scroller. val a = context.theme.obtainStyledAttributes(attrs, R.styleable.StatusViewScroller, 0, 0) @@ -444,7 +448,7 @@ class StatusView @JvmOverloads constructor( textColorLabelsIncomplete = a.getColor(R.styleable.StatusViewScroller_textColorLabelsIncomplete, textColorLabels) textColorLabelCurrent = a.getColor(R.styleable.StatusViewScroller_textColorLabelsCurrent, textColorLabelsIncomplete) lineColorIncomplete = a.getColor(R.styleable.StatusViewScroller_lineColorIncomplete, lineColor) - lineColorCurrent= a.getColor(R.styleable.StatusViewScroller_lineColorCurrent, lineColorIncomplete) + lineColorCurrent = a.getColor(R.styleable.StatusViewScroller_lineColorCurrent, lineColorIncomplete) circleFillColorIncomplete = a.getColor(R.styleable.StatusViewScroller_circleColorIncomplete, circleFillColor) circleStrokeColorIncomplete = a.getColor(R.styleable.StatusViewScroller_circleStrokeColorIncomplete, circleStrokeColor) circleStrokeColorCurrent = a.getColor(R.styleable.StatusViewScroller_circleStrokeColorCurrent, circleStrokeColorIncomplete) @@ -454,7 +458,7 @@ class StatusView @JvmOverloads constructor( val entries = a.getTextArray(R.styleable.StatusViewScroller_android_entries) if (entries != null) { - for(entry in entries){ + for (entry in entries) { statusData.add(StatusInfo(entry.toString())) } } @@ -528,8 +532,7 @@ class StatusView @JvmOverloads constructor( mTextPaintLabelCurrent.typeface = this } - - + propsIntialisedOnce = true } private fun initCirclePaints() { @@ -549,7 +552,7 @@ class StatusView @JvmOverloads constructor( } } - if(isShowingCurrentStatus()){ + if (isShowingCurrentStatus()) { if (mCircleStrokePaintCurrent == null) { mCircleStrokePaintCurrent = Paint(mCircleStrokePaint) mCircleStrokePaintCurrent?.color = circleStrokeColorCurrent @@ -576,7 +579,7 @@ class StatusView @JvmOverloads constructor( } } - if(isShowingCurrentStatus()){ + if (isShowingCurrentStatus()) { if (mCircleFillPaintCurrent == null) { mCircleFillPaintCurrent = Paint(mCircleFillPaint) mCircleFillPaintCurrent?.color = circleFillColorCurrent @@ -584,8 +587,8 @@ class StatusView @JvmOverloads constructor( } } else { mCircleFillPaint = null - mCircleFillPaintIncomplete =null - mCircleFillPaintCurrent=null + mCircleFillPaintIncomplete = null + mCircleFillPaintCurrent = null } } @@ -593,48 +596,73 @@ class StatusView @JvmOverloads constructor( private fun isShowingIncompleteStatus() = currentCount > INVALID_STATUS_COUNT && currentCount < stepCount - private fun isShowingCurrentStatus()= + private fun isShowingCurrentStatus() = currentCount in 1..stepCount override fun getSuggestedMinimumWidth(): Int { + if (stepCount <= 0) { + return 0 + } + + lineLengthComputed = lineLength - var extraWidth = if(strictObeyLineLength){// extra width required by status at extreme positions + var extraWidth = if (strictObeyLineLength) {// extra width required by status at extreme positions setWidthDataForObeyingLineLength() - }else{ + } else { setWidthDataForObeyingStatusText() } - if(isShowingCurrentStatus()){ - extraWidth += (currentStatusRadius-circleRadius)*2 + if (stepCount == 1) { + extraWidth *= 2 } - - if(stepCount==1) { - extraWidth *= 2 + if (isShowingCurrentStatus()) { + extraWidth += (currentStatusRadius - circleRadius) * 2 } - return ((stepCount * (2 * (circleRadius + (circleStrokeWidth/2)))) + - ((stepCount - 1) * ( lineLengthComputed + (lineGap * 2))) + extraWidth).toInt() + + return ((stepCount * (2 * (circleRadius + (circleStrokeWidth / 2)))) + + ((stepCount - 1) * (lineLengthComputed + (lineGap * 2))) + extraWidth).toInt() } + override fun getSuggestedMinimumHeight(): Int { + if (stepCount <= 0) { + return 0 + } + + var maxLabelHeight = 0.0f + for (item in statusData) { + maxLabelHeight = Math.max(maxLabelHeight, setLabelsHeight(mTextPaintStatus, item)) + } - override fun getSuggestedMinimumHeight(): Int { + val circleAndStatusTextHeight = if (isShowingCurrentStatus()) { + val topRadius = currentStatusRadius + val labelHeightCurrentStatus = if (statusData.size > 0 && currentCountIndex() in 0..(statusData.size-1)) { + statusData[currentCountIndex()].height + } else { + 0.0f + } - var labelHeight = 0.0f - for(item in statusData){ - labelHeight = Math.max(labelHeight,setLabelsHeight(mTextPaintStatus,item)) + val bottomRadiusAndText = Math.max(currentStatusRadius + labelHeightCurrentStatus, circleRadius + maxLabelHeight) + + topRadius + bottomRadiusAndText + + } else { + circleRadius * 2 + maxLabelHeight } - if(statusData.size>0){ - labelHeight+=statusTopMargin + + val statusTopMargin = if (statusData.size > 0) { + statusTopMargin + } else { + 0.0f } - val circleRadius = if(isShowingCurrentStatus()) currentStatusRadius else circleRadius - return (((circleRadius * 2)+circleStrokeWidth) + labelHeight).toInt() + return (circleAndStatusTextHeight + circleStrokeWidth + statusTopMargin).toInt() } @@ -642,12 +670,6 @@ class StatusView @JvmOverloads constructor( val desiredWidth = paddingLeft + paddingRight + suggestedMinimumWidth val desiredHeight = paddingTop + paddingBottom + suggestedMinimumHeight - /* val measureSpecWidth = MeasureSpec.getMode(widthMeasureSpec) - val measureSpecHeight = MeasureSpec.getMode(heightMeasureSpec) - *//*if(measureSpecHeight!=MeasureSpec.AT_MOST || measureSpecWidth!=MeasureSpec.AT_MOST){ - throw IllegalStateException("Width and height should be wrap_content") - }*//* -*/ val measuredWidth = resolveSize(desiredWidth, widthMeasureSpec) val measuredHeight = resolveSize(desiredHeight, heightMeasureSpec) @@ -655,7 +677,7 @@ class StatusView @JvmOverloads constructor( } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - setDrawingDimensions() + setDrawingDimensions() } override fun onDraw(canvas: Canvas?) { @@ -686,7 +708,7 @@ class StatusView @JvmOverloads constructor( } if (item.labelData != null) { - if (item.labelData.staticLayout!=null) { + if (item.labelData.staticLayout != null) { canvas?.save() canvas?.translate(item.labelData.x, item.labelData.y) @@ -715,62 +737,62 @@ class StatusView @JvmOverloads constructor( val lastPoint = PointF()//For keeping reference where last point was drawn lastPoint.x = paddingLeft.toFloat() + (circleStrokeWidth / 2) lastPoint.y = paddingTop.toFloat() + (circleRadius + (circleStrokeWidth / 2)) - if(isShowingCurrentStatus()) lastPoint.y += currentStatusRadius-circleRadius + if (isShowingCurrentStatus()) lastPoint.y += currentStatusRadius - circleRadius for (pos in 0 until stepCount) { var circleStrokePaint: Paint? - var circleFillPaint : Paint? - var textPaintLabel : TextPaint - var linePaint : Paint + var circleFillPaint: Paint? + var textPaintLabel: TextPaint + var linePaint: Paint var itemDrawable: Drawable? var circleRadius = this.circleRadius - if(isShowingCurrentStatus() && pos==(currentCount-1)){ + if (isShowingCurrentStatus() && pos == (currentCount - 1)) { circleRadius = currentStatusRadius circleStrokePaint = mCircleStrokePaintCurrent circleFillPaint = mCircleFillPaintCurrent textPaintLabel = mTextPaintLabelCurrent linePaint = mLinePaintCurrent itemDrawable = currentDrawable - }else if (isShowingIncompleteStatus() && pos in (currentCount)..stepCount) { + } else if (isShowingIncompleteStatus() && pos in (currentCount)..stepCount) { circleStrokePaint = mCircleStrokePaintIncomplete circleFillPaint = mCircleFillPaintIncomplete textPaintLabel = mTextPaintLabelsIncomplete linePaint = mLinePaintIncomplete itemDrawable = incompleteDrawable - }else{ + } else { - circleStrokePaint = this.mCircleStrokePaint - circleFillPaint = this.mCircleFillPaint - textPaintLabel = this.mTextPaintLabels - linePaint = this.mLinePaint - itemDrawable = completeDrawable + circleStrokePaint = this.mCircleStrokePaint + circleFillPaint = this.mCircleFillPaint + textPaintLabel = this.mTextPaintLabels + linePaint = this.mLinePaint + itemDrawable = completeDrawable } - var lineItem: StatusView.LineItem? = null - var statusItemText: StatusView.LabelItemText? = null - var labelItemText: StatusView.StatusItemText? = null + var lineItem: LineItem? = null + var statusItemText: LabelItemText? = null + var labelItemText: StatusItemText? = null - if(pos==0){ - if(statusData.size>0){ - lastPoint.x+= Math.max(0.0f, (statusData[0].width - minStatusWidthExtremes(pos))/2) + if (pos == 0) { + if (statusData.size > 0) { + lastPoint.x += Math.max(0.0f, (statusData[0].width - minStatusWidthExtremes(pos)) / 2) } - }else{ + } else { lastPoint.x += lineGap lineItem = LineItem(PointF(lastPoint.x, lastPoint.y), PointF(lastPoint.x + lineLengthComputed, lastPoint.y), linePaint) - lastPoint.x = lineItem.end.x + lineGap + (circleStrokeWidth / 2) + lastPoint.x = lineItem.end.x + lineGap + (circleStrokeWidth / 2) } val circleItem = CircleItem(PointF((lastPoint.x + circleRadius), lastPoint.y), circleRadius, circleStrokePaint, circleFillPaint) lastPoint.x += ((circleRadius) * 2.0f) + (circleStrokeWidth / 2) - if(posminStatusWidthWidestPos){ + if (widestLineData.widestStatus.width > minStatusWidthWidestPos) { lineLengthComputed += widestLineData.widestStatus.width - minStatusWidthWidestPos } widestLineData.subordinateWidestStatus?.run { - //in case increasing linelength as per widest Status does not satisfy statusWidth for second widest - //This can occur if widestStatus belongs to currentStatus and has a zoomed radius + //in case increasing linelength as per widest Status does not satisfy statusWidth for second widest + //This can occur if widestStatus belongs to currentStatus and has a zoomed radius val minStatusWidth = minStatusWidth(pos) if (width > minStatusWidth) { @@ -889,37 +912,37 @@ class StatusView @JvmOverloads constructor( else -> item.width = widestLineData.widestStatus.width } - if(minStatusAdjacentMargin> 0 && !addMinStatusMargin && pos in 1 until statusData.size){ - if(minStatusWidth(pos)+ minStatusWidth(pos-1) - (item.width+statusData[pos-1].width) 0 && !addMinStatusMargin && pos in 1 until statusData.size) { + if (minStatusWidth(pos) + minStatusWidth(pos - 1) - (item.width + statusData[pos - 1].width) < minStatusAdjacentMargin) { addMinStatusMargin = true } } } - if(addMinStatusMargin){ + if (addMinStatusMargin) { //add additional padding only if it is required - lineLengthComputed+= minStatusAdjacentMargin + lineLengthComputed += minStatusAdjacentMargin } return extraWidth } - private fun minStatusWidth(pos:Int): Float { + private fun minStatusWidth(pos: Int): Float { var circleRadius = this.circleRadius - val lineWidth = (lineLengthComputed + lineGap*2) + val lineWidth = (lineLengthComputed + lineGap * 2) - if(isShowingCurrentStatus() && pos==currentCountIndex()){ + if (isShowingCurrentStatus() && pos == currentCountIndex()) { circleRadius = currentStatusRadius } - return (2 * circleRadius + circleStrokeWidth) + lineWidth + return (2 * circleRadius + circleStrokeWidth) + lineWidth } - private fun minStatusWidthExtremes(pos:Int): Float { + private fun minStatusWidthExtremes(pos: Int): Float { var circleRadius = this.circleRadius - if(isShowingCurrentStatus() && pos==currentCountIndex()){ + if (isShowingCurrentStatus() && pos == currentCountIndex()) { circleRadius = currentStatusRadius } return (2 * circleRadius + circleStrokeWidth) @@ -928,7 +951,7 @@ class StatusView @JvmOverloads constructor( /** * Maximum width that a text would need to be drawn */ - private fun getTextWidth(paint:Paint, text:String):Float{ + private fun getTextWidth(paint: Paint, text: String): Float { return paint.measureText(text) } @@ -938,12 +961,12 @@ class StatusView @JvmOverloads constructor( * @param labelInfo LabelInfo which contains width and text * @return Height that the label would require */ - private fun setLabelsHeight(textPaint:TextPaint, labelInfo: StatusInfo):Float{ + private fun setLabelsHeight(textPaint: TextPaint, labelInfo: StatusInfo): Float { val staticLayoutHeight = getStaticLayout(labelInfo.text, textPaint, labelInfo.width) labelInfo.staticLayout = staticLayoutHeight - labelInfo.height = staticLayoutHeight.height.toFloat() + labelInfo.height = staticLayoutHeight.height.toFloat() return labelInfo.height - } + } /** @@ -968,15 +991,15 @@ class StatusView @JvmOverloads constructor( * @param extremeLeftStatusWidth Widest line in status at extreme left * @param extremeRightStatusWidth Widest line in status at extreme right */ - class StatusTextWidthInfo(var widestStatus:StatusWidth, var subordinateWidestStatus:StatusWidth?=null, - var extremeLeftStatusWidth:Float=0.0f, var extremeRightStatusWidth:Float=0.0f) + class StatusTextWidthInfo(var widestStatus: StatusWidth, var subordinateWidestStatus: StatusWidth? = null, + var extremeLeftStatusWidth: Float = 0.0f, var extremeRightStatusWidth: Float = 0.0f) /** * Stores Status Width Data * @param width widestStatus * @param pos Status position */ - class StatusWidth(var width: Float = 0.0f, var pos:Int=-1) + class StatusWidth(var width: Float = 0.0f, var pos: Int = -1) /** * Calculates parameters described in StatusTextWidthInfo @@ -984,7 +1007,7 @@ class StatusView @JvmOverloads constructor( * @param paint Paint required to draw the statuses * */ - private fun getStatusTextWidthInfo(list:List, paint:TextPaint):StatusTextWidthInfo{ + private fun getStatusTextWidthInfo(list: List, paint: TextPaint): StatusTextWidthInfo { val widestStatus = StatusWidth() var subordinateWidestStatus: StatusWidth? = null @@ -1014,7 +1037,7 @@ class StatusView @JvmOverloads constructor( } when (pos) { - 0 -> statusWidthInfo.extremeLeftStatusWidth= result + 0 -> statusWidthInfo.extremeLeftStatusWidth = result stepCountIndex() -> statusWidthInfo.extremeRightStatusWidth = result } @@ -1032,13 +1055,13 @@ class StatusView @JvmOverloads constructor( * @param paint Text paint for the text * @return widest line in a particular status */ - private fun getStatusWidth(text:String, paint: TextPaint):Float{ + private fun getStatusWidth(text: String, paint: TextPaint): Float { - val arr:List = text.split('\n') + val arr: List = text.split('\n') var result = 0.0f - for(sub in arr){ + for (sub in arr) { val subWidth = paint.measureText(sub) - result = if(subWidth>result) subWidth else result + result = if (subWidth > result) subWidth else result } return result @@ -1060,21 +1083,21 @@ class StatusView @JvmOverloads constructor( /** * actual position in list for currentCount */ - private fun currentCountIndex() = currentCount-1 + private fun currentCountIndex() = currentCount - 1 /** * actual position in list for stepCount */ - private fun stepCountIndex() = stepCount-1 + private fun stepCountIndex() = stepCount - 1 /** * Delegate property used to requestLayout on value set after executing a custom function */ - inner class OnLayoutProp (private var field:T, private inline var func:()->Unit={}){ - operator fun setValue(thisRef: Any?,p: KProperty<*>,v: T) { + inner class OnLayoutProp(private var field: T, private inline var func: () -> Unit = {}) { + operator fun setValue(thisRef: Any?, p: KProperty<*>, v: T) { field = v - if(ViewCompat.isLaidOut(this@StatusView)){ + if (propsIntialisedOnce) { drawingData.clear() func() requestLayout() @@ -1082,7 +1105,8 @@ class StatusView @JvmOverloads constructor( } } - operator fun getValue(thisRef: Any?,p: KProperty<*>):T{ + + operator fun getValue(thisRef: Any?, p: KProperty<*>): T { return field } @@ -1091,29 +1115,30 @@ class StatusView @JvmOverloads constructor( /** * Delegate Property used to invalidate on value set after executing a custom function */ - inner class OnValidateProp (private var field:T, private inline var func:()->Unit={}){ - operator fun setValue(thisRef: Any?,p: KProperty<*>,v: T) { + inner class OnValidateProp(private var field: T, private inline var func: () -> Unit = {}) { + operator fun setValue(thisRef: Any?, p: KProperty<*>, v: T) { field = v - if(ViewCompat.isLaidOut(this@StatusView)){ + if (propsIntialisedOnce) { func() invalidate() } } - operator fun getValue(thisRef: Any?,p: KProperty<*>):T{ + + operator fun getValue(thisRef: Any?, p: KProperty<*>): T { return field } } - private fun Float.pxValue(unit:Int = TypedValue.COMPLEX_UNIT_DIP):Float{ - return TypedValue.applyDimension(unit,this,resources.displayMetrics) + private fun Float.pxValue(unit: Int = TypedValue.COMPLEX_UNIT_DIP): Float { + return TypedValue.applyDimension(unit, this, resources.displayMetrics) } fun getScrollPosForStep(count: Int): Int { val posIndex = count - 1 - return if (posIndex> 0 && posIndex < drawingData.size) { + return if (posIndex > 0 && posIndex < drawingData.size) { drawingData[posIndex].circleItem.center.x.toInt() } else { 0 diff --git a/app/src/main/res/values/attrs.xml b/stepview/src/main/res/values/attrs.xml similarity index 100% rename from app/src/main/res/values/attrs.xml rename to stepview/src/main/res/values/attrs.xml diff --git a/stepview/src/main/res/values/strings.xml b/stepview/src/main/res/values/strings.xml new file mode 100644 index 0000000..27ecf23 --- /dev/null +++ b/stepview/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + stepview +