113 | * 09-21 13:59:30.594 8977 8977 D ChartRectIndicator2: onAnimationUpdate: mAnimatedValue=0.9992871
114 | * 09-21 13:59:30.594 8977 8977 D ChartRectIndicator2: draw: 0, 0.9992871, 145.3547
115 | * 09-21 13:59:30.609 8977 8977 D ChartRectIndicator2: onAnimationRepeat:
116 | * 09-21 13:59:30.609 8977 8977 D ChartRectIndicator2: onAnimationRepeat: mCurrAnimatorState=1
117 | * 09-21 13:59:30.609 8977 8977 D ChartRectIndicator2: onAnimationUpdate: mAnimatedValue=1.0
118 | * 09-21 13:59:30.610 8977 8977 D ChartRectIndicator2: draw: 1, 1.0, 115.33333
119 | * 09-21 13:59:30.626 8977 8977 D ChartRectIndicator2: onAnimationUpdate: mAnimatedValue=7.1290135E-4
120 | * 09-21 13:59:30.626 8977 8977 D ChartRectIndicator2: draw: 1, 7.1290135E-4, 175.29056
121 | * 09-21 13:59:30.642 8977 8977 D ChartRectIndicator2: onAnimationUpdate: mAnimatedValue=0.0026845932
122 | * 09-21 13:59:30.643 8977 8977 D ChartRectIndicator2: draw: 1, 0.0026845932, 175.17226
123 | */
124 | private void changeState() {
125 | if (repeatRunned) {
126 | if (mAnimatedValue > 0.9f) {
127 | //do nothing
128 | } else {
129 | repeatRunned = false;
130 |
131 | mCurrAnimatorState++;
132 | Log.d(TAG, "onAnimationRepeat: mCurrAnimatorState=" + mCurrAnimatorState);
133 | if (mCurrAnimatorState > mCount + 1) {
134 | Log.d(TAG, "onAnimationRepeat: set mCurrAnimatorState to 0");
135 | mCurrAnimatorState = 0;
136 | }
137 | }
138 | }
139 | }
140 |
141 | @Override
142 | protected void draw(Canvas canvas, Paint paint) {
143 |
144 | changeState();
145 |
146 | float rectWidth = getWidth() / 25;
147 | float rectSpace = rectWidth;
148 | float startX = (getWidth() - (rectWidth * mCount + rectSpace * (mCount - 1))) / 2;
149 | float bottomY = getHeight() / 1.5f;
150 |
151 | for (int i = 0; i < mCount; i++) {
152 |
153 | //mCurrAnimatorState [0, mCount+1]
154 | // =0 不显示第2 3 4 5条
155 | // =1 不显示第3 4 5条
156 | // =2 不显示第4 5条
157 | // =3 不显示第5条
158 | // >= 4 全显示
159 | if (i > mCurrAnimatorState) {
160 | break;
161 | }
162 |
163 | canvas.save();
164 |
165 | //mAnimatedValue 0 ~ 0.5 ~ 1
166 | //range 0 ~ 0.5 ~ 0
167 | float range = (0.5f - Math.abs(mAnimatedValue - 0.5f));
168 | float offsetHeight = range * rectMax;
169 |
170 | int j = i % 3;//3条一显示周期
171 | rectF.setEmpty();
172 | if (i == mCurrAnimatorState) {
173 | //当前,从无到显示
174 | Log.d(TAG, "draw: " + i + ", " + mAnimatedValue + ", " + (bottomY - (j + 1) * rectMax * mAnimatedValue));
175 | rectF.set(startX + i * (rectWidth + rectSpace),
176 | bottomY - (j + 1) * rectMax * mAnimatedValue,
177 | startX + i * (rectWidth + rectSpace) + rectWidth,
178 | bottomY);
179 | } else {
180 | //其他,抖动效果
181 | rectF.set(startX + i * (rectWidth + rectSpace),
182 | bottomY - (j + 1) * rectMax - offsetHeight,
183 | startX + i * (rectWidth + rectSpace) + rectWidth,
184 | bottomY);
185 | }
186 | canvas.drawRect(rectF, paint);
187 | canvas.restore();
188 | }
189 |
190 |
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/library/src/main/java/com/example/mzy/indicators/Rect/RectJumpMoveIndicator.java:
--------------------------------------------------------------------------------
1 | package com.example.mzy.indicators.Rect;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.content.Context;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.RectF;
10 | import android.util.Log;
11 | import android.view.animation.LinearInterpolator;
12 |
13 | import com.example.mzy.indicators.IndicatorDrawable;
14 |
15 | import java.util.ArrayList;
16 |
17 | /**
18 | * Created by mazhengyang on 18-12-7.
19 | */
20 |
21 | public class RectJumpMoveIndicator extends IndicatorDrawable {
22 |
23 | //TODO 暂未解决向右移动,倒数第二个方块伸缩效果,向左移动,顺数第二个方块伸缩效果
24 |
25 | private final String TAG = RectJumpMoveIndicator.class.getSimpleName();
26 |
27 | private final int mCount = 5;
28 |
29 | private int mState = 0;//0静止,1向右移动,2向左移动
30 | private float mPreviousAnimatedValue = 0.0f;
31 | private float mAnimatedValue = 0.0f;
32 |
33 | private RectF rectF = new RectF();
34 |
35 | private boolean drawAssist = false;
36 |
37 | private int currentJumpIndex = 0;
38 |
39 | public RectJumpMoveIndicator(Context context, int indicatorColor, int indicatorSpeed) {
40 | Log.d(TAG, "RectJumpMoveIndicator: ");
41 | this.indicatorColor = indicatorColor;
42 | this.indicatorSpeed = indicatorSpeed;
43 | if (indicatorSpeed <= 0) {
44 | this.indicatorSpeed = 2000;
45 | }
46 |
47 | init(context);
48 | }
49 |
50 | @Override
51 | protected void init(Context context) {
52 | Log.d(TAG, "init: ");
53 | mPaint.setAntiAlias(true);
54 | mPaint.setStyle(Paint.Style.FILL);
55 | mPaint.setStrokeWidth(dip2px(context, 1.0f));
56 | mPaint.setColor(indicatorColor);
57 | }
58 |
59 | @Override
60 | protected ArrayList
8 | *
10 | * ## Standard Coordinate:
17 | *
107 | * NOTE: init or update by {@link #initAllVertexesToStandard() }
108 | *
109 | * @see #firstVertex
110 | * @see #starVertexes
111 | */
112 | private VertexF[] vertexes;
113 |
114 | // endregion
115 |
116 | private void initAllVertexesToStandard() {
117 | if (firstVertex == null) {
118 | firstVertex = new VertexF(starVertexes[0], starVertexes[1]);
119 | } else {
120 | firstVertex.x = starVertexes[0];
121 | firstVertex.y = starVertexes[1];
122 | }
123 |
124 | // create all 10 vertexes into #vertexes
125 | if (vertexes == null) {
126 | vertexes = new VertexF[10];
127 | vertexes[0] = firstVertex;
128 |
129 | for (int i = 1; i < 10; i++) {
130 | vertexes[i] = new VertexF();
131 | vertexes[i - 1].next = vertexes[i];
132 | }
133 |
134 | // link tail and head
135 | vertexes[9].next = vertexes[0];
136 | }
137 |
138 | // update all 5 outer vertexes.
139 | VertexF current = firstVertex;
140 | for (int i = 0; i < 5; i++) {
141 | current.x = starVertexes[i * 2];
142 | current.y = starVertexes[i * 2 + 1];
143 |
144 | current = current.next.next;
145 | }
146 |
147 | // update all 5 inner vertexes.
148 | VertexF prevOuter = firstVertex;
149 | for (int i = 0; i < 5; i++) {
150 | VertexF innerV = prevOuter.next;
151 |
152 | innerV.x = (prevOuter.x + innerV.next.x) / 2f;
153 | innerV.y = (prevOuter.y + innerV.next.y) / 2f;
154 |
155 | prevOuter = innerV.next;
156 | }
157 | }
158 |
159 | /**
160 | * Get vertex at index in {@link #vertexes}
161 | *
162 | * @param index see {@link #vertexes}
163 | */
164 | public VertexF getVertex(int index) {
165 | return vertexes[index];
166 | }
167 |
168 | public RectF getOuterRect() {
169 | return new RectF(outerRect);
170 | }
171 |
172 | /**
173 | * Keep the star's outer bounds exactly.
174 | * NOTE: call this after any vertex value changed.
175 | */
176 | private void updateOuterRect() {
177 | outerRect.top = vertexes[2].y;
178 | outerRect.right = vertexes[4].x;
179 | outerRect.bottom = vertexes[8].y;
180 | outerRect.left = vertexes[0].x;
181 | }
182 |
183 | private void offsetStar(float left, float top) {
184 | for (int i = 0; i < vertexes.length; i++) {
185 | vertexes[i].x += left;
186 | vertexes[i].y += top;
187 | }
188 | }
189 |
190 | private void changeScaleFactor(float newFactor) {
191 | float scale = newFactor / currentScaleFactor;
192 | if (scale == 1f) return;
193 | for (int i = 0; i < vertexes.length; i++) {
194 | vertexes[i].x *= scale;
195 | vertexes[i].y *= scale;
196 | }
197 | currentScaleFactor = newFactor;
198 | }
199 |
200 | /**
201 | * change the thickness of star.
202 | * value {@link #DEFAULT_THICKNESS}is about to make a standard star.
203 | *
204 | * @param factor between {@link #MIN_THICKNESS} and {@link #MAX_THICKNESS}.
205 | */
206 | public void setThickness(float factor) {
207 | if (currentThicknessFactor == factor) return;
208 | float oldScale = currentScaleFactor;
209 | float left = outerRect.left;
210 | float top = outerRect.top;
211 |
212 | reset(factor);
213 |
214 | changeScaleFactor(oldScale);
215 | moveStarTo(left, top);
216 | }
217 |
218 | private void setThicknessOnStandardCoordinate(float thicknessFactor) {
219 | if (thicknessFactor < MIN_THICKNESS) {
220 | thicknessFactor = MIN_THICKNESS;
221 | } else if (thicknessFactor > MAX_THICKNESS) {
222 | thicknessFactor = MAX_THICKNESS;
223 | }
224 |
225 | for (int i = 1; i < vertexes.length; i += 2) {
226 | vertexes[i].x *= thicknessFactor;
227 | vertexes[i].y *= thicknessFactor;
228 | }
229 |
230 | currentThicknessFactor = thicknessFactor;
231 | }
232 |
233 | /**
234 | * reverse Y, and move to y=0
235 | */
236 | private void adjustCoordinate() {
237 | float offsetX = -outerRect.left;
238 | float offsetY = outerRect.top;
239 |
240 | for (int i = 0; i < vertexes.length; i++) {
241 | vertexes[i].y = -vertexes[i].y + offsetY;
242 | vertexes[i].x += offsetX;
243 |
244 | // standard value is in radius = 1f, so..
245 | vertexes[i].x /= 2f;
246 | vertexes[i].y /= 2f;
247 | }
248 |
249 | updateOuterRect();
250 | }
251 |
252 | /**
253 | * ratio = height / width. width is think as 1f, because the star's width is lager.
254 | * NOTE: In the "Standard Coordinate"
255 | *
256 | * @return ratio = height / width.
257 | */
258 | public static float getOuterRectAspectRatio() {
259 | return aspectRatio;
260 | }
261 |
262 | public static float getStarWidth(float starHeight) {
263 | return starHeight / getOuterRectAspectRatio();
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/library/src/main/java/com/example/mzy/indicators/LoadingIndicator.java:
--------------------------------------------------------------------------------
1 | package com.example.mzy.indicators;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Rect;
8 | import android.graphics.drawable.Drawable;
9 | import android.text.TextUtils;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.View;
13 |
14 | import com.example.mzy.indicators.Circle.ArcRotateIndicator;
15 | import com.example.mzy.indicators.Circle.ArcRotateScaleIndicator;
16 | import com.example.mzy.indicators.Circle.BasketBallIndicator;
17 | import com.example.mzy.indicators.Circle.CircleCollisionIndicator;
18 | import com.example.mzy.indicators.Circle.CircleJumpIndicator;
19 | import com.example.mzy.indicators.Circle.CircleRotateIndicator;
20 | import com.example.mzy.indicators.Circle.CircleRotateScaleIndicator;
21 | import com.example.mzy.indicators.Circle.CircleScaleIndicator;
22 | import com.example.mzy.indicators.Circle.CircleTriangleIndicator;
23 | import com.example.mzy.indicators.Circle.CircleWaveIndicator;
24 | import com.example.mzy.indicators.Circle.DropIndicator;
25 | import com.example.mzy.indicators.Circle.TrackIndicator;
26 | import com.example.mzy.indicators.Rect.ChartRectIndicator1;
27 | import com.example.mzy.indicators.Rect.ChartRectIndicator2;
28 | import com.example.mzy.indicators.Rect.ParallelogramIndicator;
29 | import com.example.mzy.indicators.Rect.RectJumpMoveIndicator;
30 | import com.example.mzy.indicators.Star.StarIndicator;
31 |
32 | /**
33 | * Created by mazhengyang on 18-9-18.
34 | */
35 |
36 | public class LoadingIndicator extends View {
37 |
38 | private final String TAG = LoadingIndicator.class.getSimpleName();
39 |
40 | private int mMinWidth;
41 | private int mMaxWidth;
42 | private int mMinHeight;
43 | private int mMaxHeight;
44 | private String indicatorName;
45 | private int indicatorColor;
46 | private int indicatorSpeed;
47 |
48 | private IndicatorDrawable mIndicator;
49 |
50 | public LoadingIndicator(Context context) {
51 | super(context);
52 | init(context, null, 0, 0);
53 | }
54 |
55 | public LoadingIndicator(Context context, AttributeSet attrs) {
56 | super(context, attrs);
57 | init(context, attrs, 0, R.style.LoadingIndicator);
58 | }
59 |
60 | public LoadingIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
61 | super(context, attrs, defStyleAttr);
62 | init(context, attrs, defStyleAttr, R.style.LoadingIndicator);
63 | }
64 |
65 | private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
66 |
67 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingIndicator, defStyleAttr, defStyleRes);
68 | mMinWidth = ta.getDimensionPixelSize(R.styleable.LoadingIndicator_minWidth, 60);
69 | mMaxWidth = ta.getDimensionPixelSize(R.styleable.LoadingIndicator_maxWidth, 60);
70 | mMinHeight = ta.getDimensionPixelSize(R.styleable.LoadingIndicator_minHeight, 60);
71 | mMaxHeight = ta.getDimensionPixelSize(R.styleable.LoadingIndicator_maxHeight, 60);
72 | indicatorName = ta.getString(R.styleable.LoadingIndicator_indicatorName);
73 | indicatorColor = ta.getColor(R.styleable.LoadingIndicator_indicatorColor, Color.WHITE);
74 | indicatorSpeed = ta.getInteger(R.styleable.LoadingIndicator_indicatorSpeed, 0);
75 | ta.recycle();
76 |
77 | Log.d(TAG, "init: density=" + context.getResources().getDisplayMetrics().density);
78 | Log.d(TAG, "init: mMinWidth=" + mMinWidth + ", mMaxWidth=" + mMaxWidth);
79 | Log.d(TAG, "init: mMinHeight=" + mMinHeight + ", mMaxHeight=" + mMaxHeight);
80 | Log.d(TAG, "init: indicatorName=" + indicatorName);
81 | Log.d(TAG, "init: indicatorColor=" + indicatorColor);
82 | Log.d(TAG, "init: indicatorSpeed=" + indicatorSpeed);
83 |
84 | IndicatorDrawable indicatorDrawable = getIndicator(indicatorName, context);
85 | // if (indicatorDrawable == null) {
86 | // Log.e(TAG, "init: indicatorDrawable is null, set default: BasketBallIndicator");
87 | // indicatorDrawable = new BasketBallIndicator(context, indicatorColor);
88 | // }
89 | indicatorDrawable.setCallback(this);
90 | mIndicator = indicatorDrawable;
91 | }
92 |
93 | public IndicatorDrawable getIndicator(String indicatorName, Context context) {
94 | if (TextUtils.isEmpty(indicatorName)) {
95 | return null;
96 | }
97 |
98 | if ("BasketBallIndicator".equals(indicatorName)) {
99 | return new BasketBallIndicator(context, indicatorColor, indicatorSpeed);
100 | } else if ("StarIndicator".equals(indicatorName)) {
101 | return new StarIndicator(context, indicatorColor, indicatorSpeed);
102 | } else if ("CircleScaleIndicator".equals(indicatorName)) {
103 | return new CircleScaleIndicator(context, indicatorColor, indicatorSpeed);
104 | } else if ("CircleWaveIndicator".equals(indicatorName)) {
105 | return new CircleWaveIndicator(context, indicatorColor, indicatorSpeed);
106 | } else if ("CircleJumpIndicator".equals(indicatorName)) {
107 | return new CircleJumpIndicator(context, indicatorColor, indicatorSpeed);
108 | } else if ("CircleCollisionIndicator".equals(indicatorName)) {
109 | return new CircleCollisionIndicator(context, indicatorColor, indicatorSpeed);
110 | } else if ("DropIndicator".equals(indicatorName)) {
111 | return new DropIndicator(context, indicatorColor, indicatorSpeed);
112 | } else if ("TrackIndicator".equals(indicatorName)) {
113 | return new TrackIndicator(context, indicatorColor, indicatorSpeed);
114 | } else if ("CircleRotateScaleIndicator".equals(indicatorName)) {
115 | return new CircleRotateScaleIndicator(context, indicatorColor, indicatorSpeed);
116 | } else if ("CircleTriangleIndicator".equals(indicatorName)) {
117 | return new CircleTriangleIndicator(context, indicatorColor, indicatorSpeed);
118 | } else if ("ChartRectIndicator1".equals(indicatorName)) {
119 | return new ChartRectIndicator1(context, indicatorColor, indicatorSpeed);
120 | } else if ("ChartRectIndicator2".equals(indicatorName)) {
121 | return new ChartRectIndicator2(context, indicatorColor, indicatorSpeed);
122 | } else if ("ArcRotateIndicator".equals(indicatorName)) {
123 | return new ArcRotateIndicator(context, indicatorColor, indicatorSpeed);
124 | } else if ("RectJumpMoveIndicator".equals(indicatorName)) {
125 | return new RectJumpMoveIndicator(context, indicatorColor, indicatorSpeed);
126 | } else if ("ArcRotateScaleIndicator".equals(indicatorName)) {
127 | return new ArcRotateScaleIndicator(context, indicatorColor, indicatorSpeed);
128 | } else if ("ParallelogramIndicator".equals(indicatorName)) {
129 | return new ParallelogramIndicator(context, indicatorColor, indicatorSpeed);
130 | } else if ("CircleRotateIndicator".equals(indicatorName)) {
131 | return new CircleRotateIndicator(context, indicatorColor, indicatorSpeed);
132 | }
133 | return null;
134 | }
135 |
136 | @Override
137 | protected boolean verifyDrawable(Drawable who) {
138 | return who == mIndicator || super.verifyDrawable(who);
139 | }
140 |
141 | @Override
142 | public void invalidateDrawable(Drawable dr) {
143 | if (verifyDrawable(dr)) {
144 | Rect dirty = dr.getBounds();
145 | int scrollX = getScrollX() + getPaddingLeft();
146 | int scrollY = getScrollY() + getPaddingTop();
147 |
148 | invalidate(dirty.left + scrollX, dirty.top + scrollY,
149 | dirty.right + scrollX, dirty.bottom + scrollY);
150 | } else {
151 | super.invalidateDrawable(dr);
152 | }
153 | }
154 |
155 | @Override
156 | protected void onDraw(Canvas canvas) {
157 | super.onDraw(canvas);
158 |
159 | int saveCount = canvas.save();
160 | mIndicator.draw(canvas);
161 | canvas.restoreToCount(saveCount);
162 | }
163 |
164 | @Override
165 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
166 | super.onSizeChanged(w, h, oldw, oldh);
167 | updateDrawableBounds(w, h);
168 | }
169 |
170 | private void updateDrawableBounds(int w, int h) {
171 |
172 | w = w - (getPaddingRight() + getPaddingLeft());
173 | h = h - (getPaddingTop() + getPaddingBottom());
174 |
175 | Log.d(TAG, "updateDrawableBounds: w=" + w + ", h=" + h);
176 |
177 | int left = 0;
178 | int top = 0;
179 | int right = w;
180 | int bottom = h;
181 |
182 | if (mIndicator != null) {
183 | int intrinsicWidth = mIndicator.getIntrinsicWidth();
184 | int intrinsicHeight = mIndicator.getIntrinsicHeight();
185 | float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
186 | float boundAspect = (float) w / h;
187 | if (intrinsicAspect != boundAspect) {
188 | if (boundAspect > intrinsicAspect) {
189 | // New width is larger. Make it smaller to match height.
190 | final int width = (int) (h * intrinsicAspect);
191 | left = (w - width) / 2;
192 | right = left + width;
193 | } else {
194 | // New height is larger. Make it smaller to match width.
195 | final int height = (int) (w * (1 / intrinsicAspect));
196 | top = (h - height) / 2;
197 | bottom = top + height;
198 | }
199 | }
200 | Log.d(TAG, "updateDrawableBounds: left=" + left + ", top=" + top + ", right="
201 | + right + ", bottom=" + bottom);
202 | mIndicator.setBounds(left, top, right, bottom);
203 | }
204 | }
205 |
206 | @Override
207 | protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
208 | int dw = 0;
209 | int dh = 0;
210 |
211 | final Drawable d = mIndicator;
212 | if (d != null) {
213 | Log.d(TAG, "onMeasure: d.getIntrinsicWidth()=" + d.getIntrinsicWidth());
214 | Log.d(TAG, "onMeasure: d.getIntrinsicHeight()=" + d.getIntrinsicHeight());
215 |
216 | dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
217 | dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
218 | }
219 |
220 | dw = dw + (getPaddingLeft() + getPaddingRight());
221 | dh = dh + (getPaddingTop() + getPaddingBottom());
222 |
223 | final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
224 | final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
225 | setMeasuredDimension(measuredWidth, measuredHeight);
226 | }
227 |
228 | @Override
229 | protected void onAttachedToWindow() {
230 | super.onAttachedToWindow();
231 | startAnimation();
232 | }
233 |
234 | @Override
235 | protected void onDetachedFromWindow() {
236 | super.onDetachedFromWindow();
237 | stopAnimation();
238 | }
239 | //
240 | // @Override
241 | // protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
242 | // super.onVisibilityChanged(changedView, visibility);
243 | // if (visibility == View.VISIBLE) {
244 | // startAnimation();
245 | // } else {
246 | // stopAnimation();
247 | // }
248 | // }
249 |
250 | private void startAnimation() {
251 | if (getVisibility() != VISIBLE) {
252 | return;
253 | }
254 | Log.d(TAG, "startAnimation: ");
255 | mIndicator.start();
256 | postInvalidate();
257 | }
258 |
259 | private void stopAnimation() {
260 | Log.d(TAG, "stopAnimation: ");
261 | mIndicator.stop();
262 | postInvalidate();
263 | }
264 |
265 | }
266 |
--------------------------------------------------------------------------------
[Based Idea or Concept]
9 | *
11 | * The coordinate is —— toward right for x+ ,toward up for y+ .
12 | *
13 | * ## 5 outer vertexes
14 | * The outer circle's (means "circumcircle") radius is 1f, original point O is the star's center,
15 | * so, the 5 vertexes at 5 outer corner is (from top A, at clockwise order):
16 | *