91 | * This can be in handy if the vector consists of 1 path only 92 | * 93 | * @return the [RichPath] object found or null 94 | */ 95 | fun findFirstRichPath(): RichPath? { 96 | return findRichPathByIndex(0) 97 | } 98 | 99 | /** 100 | * find [RichPath] by its index or null if not found 101 | *
102 | * Note that the provided index must be the flattened index of the path 103 | *
104 | * example: 105 | *
106 | * {@code118 | * 119 | * @param index the flattened index of the path 120 | * @return the [RichPath] object found or null 121 | */ 122 | fun findRichPathByIndex(@IntRange(from = 0) index: Int): RichPath? { 123 | if (vector == null || index < 0 || index >= vector.paths.size) return null 124 | return vector.paths[index] 125 | } 126 | 127 | private fun listenToPathsUpdates() { 128 | val vector = vector ?: return 129 | for (path in vector.paths) { 130 | path.onRichPathUpdatedListener = object : OnRichPathUpdatedListener { 131 | override fun onPathUpdated() { 132 | invalidateSelf() 133 | } 134 | } 135 | } 136 | } 137 | 138 | fun addPath(path: String) { 139 | addPath(PathParser.createPathFromPathData(path)) 140 | } 141 | 142 | fun addPath(path: Path) { 143 | if (path is RichPath) { 144 | addPath(path) 145 | } else { 146 | addPath(RichPath(path)) 147 | } 148 | } 149 | 150 | private fun addPath(path: RichPath) { 151 | val vector = vector ?: return 152 | 153 | vector.paths.add(path) 154 | path.onRichPathUpdatedListener = object : OnRichPathUpdatedListener { 155 | override fun onPathUpdated() { 156 | invalidateSelf() 157 | } 158 | } 159 | invalidateSelf() 160 | } 161 | 162 | fun getTouchedPath(event: MotionEvent?): RichPath? { 163 | val vector = vector ?: return null 164 | 165 | when (event?.action) { 166 | MotionEvent.ACTION_UP -> { 167 | for (i in vector.paths.indices.reversed()) { 168 | val richPath = vector.paths[i] 169 | if (PathUtils.isTouched(richPath, event.x, event.y)) { 170 | return richPath 171 | } 172 | } 173 | } 174 | } 175 | 176 | return null 177 | } 178 | 179 | override fun draw(canvas: Canvas) { 180 | if (vector == null || vector.paths.size < 0) return 181 | 182 | for (path in vector.paths) { 183 | path.draw(canvas) 184 | } 185 | } 186 | 187 | override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { 188 | 189 | } 190 | 191 | override fun getOpacity(): Int { 192 | return PixelFormat.TRANSLUCENT 193 | } 194 | 195 | override fun setColorFilter(colorFilter: ColorFilter?) { 196 | } 197 | } -------------------------------------------------------------------------------- /richpath/src/main/java/com/richpath/RichPathView.kt: -------------------------------------------------------------------------------- 1 | package com.richpath 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Path 6 | import android.util.AttributeSet 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import androidx.annotation.DrawableRes 10 | import androidx.annotation.IntRange 11 | import androidx.appcompat.widget.AppCompatImageView 12 | import com.richpath.pathparser.PathParser 13 | import com.richpath.model.Vector 14 | import org.xmlpull.v1.XmlPullParserException 15 | import com.richpath.util.XmlParser 16 | import java.io.IOException 17 | import kotlin.math.min 18 | 19 | class RichPathView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : AppCompatImageView(context, attrs, defStyleAttr) { 20 | 21 | constructor(context: Context?, attrs: AttributeSet?): this(context, attrs, 0) 22 | 23 | private lateinit var vector: Vector 24 | private var richPathDrawable: RichPathDrawable? = null 25 | var onPathClickListener: RichPath.OnPathClickListener? = null 26 | 27 | init { 28 | init() 29 | setupAttributes(attrs) 30 | } 31 | 32 | private fun init() { 33 | //Disable hardware 34 | setLayerType(View.LAYER_TYPE_SOFTWARE, null) 35 | } 36 | 37 | private fun setupAttributes(attrs: AttributeSet?) { 38 | // Obtain a typed array of attributes 39 | val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.RichPathView, 0, 0) 40 | // Extract custom attributes into member variables 41 | val resID: Int 42 | try { 43 | resID = typedArray.getResourceId(R.styleable.RichPathView_vector, -1) 44 | } finally { // TypedArray objects are shared and must be recycled. 45 | typedArray.recycle() 46 | } 47 | 48 | if (resID != -1) { 49 | setVectorDrawable(resID) 50 | } 51 | } 52 | 53 | /** 54 | * Set a VectorDrawable resource ID. 55 | * 56 | * @param resId the resource ID for VectorDrawableCompat object. 57 | */ 58 | @SuppressLint("ResourceType") 59 | fun setVectorDrawable(@DrawableRes resId: Int) { 60 | val xpp = context.resources.getXml(resId) 61 | vector = Vector() 62 | try { 63 | XmlParser.parseVector(vector, xpp, context) 64 | richPathDrawable = RichPathDrawable(vector, scaleType) 65 | setImageDrawable(richPathDrawable) 66 | } catch (e: IOException) { 67 | e.printStackTrace() 68 | } catch (e: XmlPullParserException) { 69 | e.printStackTrace() 70 | } 71 | } 72 | 73 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 74 | val vector = vector ?: return 75 | val desiredWidth = vector.width 76 | val desiredHeight = vector.height 77 | 78 | val widthMode = MeasureSpec.getMode(widthMeasureSpec) 79 | val widthSize = MeasureSpec.getSize(widthMeasureSpec) 80 | val heightMode = MeasureSpec.getMode(heightMeasureSpec) 81 | val heightSize = MeasureSpec.getSize(heightMeasureSpec) 82 | 83 | //Measure Width 84 | val width: Int = when (widthMode) { 85 | MeasureSpec.EXACTLY -> { 86 | //Must be this size 87 | widthSize 88 | } 89 | MeasureSpec.AT_MOST -> { 90 | //Can't be bigger than... 91 | min(desiredWidth.toInt(), widthSize) 92 | } 93 | else -> { 94 | //Be whatever you want 95 | desiredWidth.toInt() 96 | } 97 | } 98 | 99 | //Measure Height 100 | val height: Int = when (heightMode) { 101 | MeasureSpec.EXACTLY -> { 102 | //Must be this size 103 | heightSize 104 | } 105 | MeasureSpec.AT_MOST -> { 106 | //Can't be bigger than... 107 | min(desiredHeight.toInt(), heightSize) 108 | } 109 | else -> { 110 | //Be whatever you want 111 | desiredHeight.toInt() 112 | } 113 | } 114 | 115 | //MUST CALL THIS 116 | setMeasuredDimension(width, height) 117 | } 118 | 119 | fun findAllRichPaths(): Array107 | * } 117 | *// index = 0 108 | * // index = 1 109 | * 110 | * 115 | *// index = 2 111 | * 112 | * 114 | *// index = 3 113 | * // index = 4 116 | *
130 | * This can be in handy if the vector consists of 1 path only 131 | * 132 | * @return the [RichPath] object found or null 133 | */ 134 | fun findFirstRichPath(): RichPath? { 135 | return richPathDrawable?.findFirstRichPath() 136 | } 137 | 138 | /** 139 | * find [RichPath] by its index or null if not found 140 | *
141 | * Note that the provided index must be the flattened index of the path 142 | *
143 | * example: 144 | *
145 | * {@code157 | * 158 | * @param index the flattened index of the path 159 | * @return the [RichPath] object found or null 160 | */ 161 | fun findRichPathByIndex(@IntRange(from = 0) index: Int): RichPath? { 162 | return richPathDrawable?.findRichPathByIndex(index) 163 | } 164 | 165 | fun addPath(path: String) { 166 | richPathDrawable?.addPath(PathParser.createPathFromPathData(path)) 167 | } 168 | 169 | fun addPath(path: Path) { 170 | richPathDrawable?.addPath(path) 171 | } 172 | 173 | override fun onTouchEvent(event: MotionEvent?): Boolean { 174 | when(event?.action) { 175 | MotionEvent.ACTION_UP -> { 176 | performClick() 177 | } 178 | } 179 | 180 | richPathDrawable?.getTouchedPath(event)?.let { richPath -> 181 | richPath.onPathClickListener?.onClick(richPath) 182 | this.onPathClickListener?.onClick(richPath) 183 | } 184 | return true 185 | } 186 | } -------------------------------------------------------------------------------- /richpath/src/main/java/com/richpath/listener/OnRichPathUpdatedListener.kt: -------------------------------------------------------------------------------- 1 | package com.richpath.listener 2 | 3 | interface OnRichPathUpdatedListener { 4 | 5 | fun onPathUpdated() 6 | 7 | } -------------------------------------------------------------------------------- /richpath/src/main/java/com/richpath/model/Group.kt: -------------------------------------------------------------------------------- 1 | package com.richpath.model 2 | 3 | import android.content.Context 4 | import android.content.res.XmlResourceParser 5 | import android.graphics.Matrix 6 | import com.richpath.util.XmlParser 7 | 8 | class Group(context: Context, xpp: XmlResourceParser) { 9 | companion object { 10 | const val TAG_NAME = "group" 11 | } 12 | 13 | var rotation = 0f 14 | private set 15 | var pivotX = 0f 16 | private set 17 | var pivotY = 0f 18 | private set 19 | var scaleX = 1f 20 | private set 21 | var scaleY = 1f 22 | private set 23 | var translateX = 0f 24 | private set 25 | var translateY = 0f 26 | private set 27 | var name: String? = null 28 | private set 29 | private var matrix: Matrix? = null 30 | 31 | init { 32 | inflate(context, xpp) 33 | } 34 | 35 | private fun inflate(context: Context, xpp: XmlResourceParser) { 36 | 37 | name = XmlParser.getAttributeString(context, xpp, "name", name) 38 | 39 | rotation = XmlParser.getAttributeFloat(xpp, "rotation", rotation) 40 | 41 | scaleX = XmlParser.getAttributeFloat(xpp, "scaleX", scaleX) 42 | 43 | scaleY = XmlParser.getAttributeFloat(xpp, "scaleY", scaleY) 44 | 45 | translateX = XmlParser.getAttributeFloat(xpp, "translateX", translateX) 46 | 47 | translateY = XmlParser.getAttributeFloat(xpp, "translateY", translateY) 48 | 49 | pivotX = XmlParser.getAttributeFloat(xpp, "pivotX", pivotX) + translateX 50 | 51 | pivotY = XmlParser.getAttributeFloat(xpp, "pivotY", pivotY) + translateY 52 | } 53 | 54 | fun matrix(): Matrix { 55 | val matrix = this.matrix?.let { it } ?: run { 56 | Matrix().apply { 57 | postTranslate(-pivotX, -pivotY) 58 | postScale(scaleX, scaleY) 59 | postRotate(rotation, 0f, 0f) 60 | postTranslate(translateX + pivotX, translateY + pivotY) 61 | } 62 | } 63 | this.matrix = matrix 64 | return matrix 65 | } 66 | 67 | fun scale(matrix: Matrix) { 68 | matrix().postConcat(matrix) 69 | } 70 | } -------------------------------------------------------------------------------- /richpath/src/main/java/com/richpath/model/Vector.kt: -------------------------------------------------------------------------------- 1 | package com.richpath.model 2 | 3 | import android.content.Context 4 | import android.content.res.XmlResourceParser 5 | import com.richpath.RichPath 6 | import com.richpath.util.XmlParser 7 | 8 | class Vector { 9 | companion object { 10 | const val TAG_NAME = "vector" 11 | } 12 | 13 | var name: String? = null 14 | private var tint = 0 15 | var height = 0f 16 | private set 17 | var width = 0f 18 | private set 19 | private var alpha = 0f 20 | 21 | private var autoMirrored = false 22 | 23 | //TODO private PorterDuff.Mode tintMode = PorterDuff.Mode.SRC_IN; 24 | 25 | var viewportWidth = 0f 26 | private set 27 | var viewportHeight = 0f 28 | private set 29 | var currentWidth = 0f 30 | var currentHeight = 0f 31 | 32 | var paths: ArrayList146 | * } 156 | *// index = 0 147 | * // index = 1 148 | * 149 | * 154 | *// index = 2 150 | * 151 | * 153 | *// index = 3 152 | * // index = 4 155 | *
source
.
92 | */
93 | fun deepCopyNodes(source: ArraynodesFrom
can morph into nodesTo
105 | */
106 | fun canMorph(nodesFrom: ArraynodesFrom
can morph into nodesTo
127 | */
128 | fun canMorph(nodes: Array