1 import android.annotation.TargetApi; 2 import android.content.Context; 3 import android.content.res.Resources; 4 import android.content.res.TypedArray; 5 import android.graphics.Canvas; 6 import android.graphics.Paint; 7 import android.graphics.RectF; 8 import android.os.Build; 9 import android.util.AttributeSet; 10 import android.view.View; 11 12 /** 13 * 类似螺纹的加载view 14 * 可以自定义的属性:颜色、旋转速度(X弧度/s) 15 */ 16 public class WhorlView extends View { 17 private static final int CIRCLE_NUM = 3; 18 19 public static final int FAST = 1; 20 public static final int MEDIUM = 0; 21 public static final int SLOW = 2; 22 23 private static final int PARALLAX_FAST = 60; 24 private static final int PARALLAX_MEDIUM = 72; 25 private static final int PARALLAX_SLOW = 90; 26 27 private static final float SWEEP_ANGLE = 90f; 28 private static final float STOKE_WIDTH = 5f; 29 private static final long REFRESH_DURATION = 16L; 30 31 private long mCircleTime; 32 private int[] mLayerColors = new int[CIRCLE_NUM]; 33 private int mCircleSpeed; 34 private int mParallaxSpeed; 35 36 public WhorlView(Context context) { 37 this(context, null, 0); 38 } 39 40 public WhorlView(Context context, AttributeSet attrs) { 41 this(context, attrs, 0); 42 } 43 44 public WhorlView(Context context, AttributeSet attrs, int defStyleAttr) { 45 super(context, attrs, defStyleAttr); 46 Resources res = getResources(); 47 final int defaultSmallColor = res.getColor(R.color.material_red); 48 final int defaultMiddleColor = res.getColor(R.color.material_green); 49 final int defaultBigColor = res.getColor(R.color.material_blue); 50 //默认外层最慢180度/s 51 final int defaultCircleSpeed = 270; 52 if (attrs != null) { 53 final TypedArray typedArray = context.obtainStyledAttributes( 54 attrs, R.styleable.WhorlView_Style); 55 mLayerColors[0] = typedArray.getColor(R.styleable.WhorlView_Style_WhorlView_SmallWhorlColor, defaultSmallColor); 56 mLayerColors[1] = typedArray.getColor(R.styleable.WhorlView_Style_WhorlView_MiddleWhorlColor, defaultMiddleColor); 57 mLayerColors[2] = typedArray.getColor(R.styleable.WhorlView_Style_WhorlView_BigWhorlColor, defaultBigColor); 58 mCircleSpeed = typedArray.getInt(R.styleable.WhorlView_Style_WhorlView_CircleSpeed, defaultCircleSpeed); 59 int index = typedArray.getInt(R.styleable.WhorlView_Style_WhorlView_Parallax, 0); 60 setParallax(index); 61 typedArray.recycle(); 62 } else { 63 mLayerColors[0] = defaultSmallColor; 64 mLayerColors[1] = defaultMiddleColor; 65 mLayerColors[2] = defaultBigColor; 66 mCircleSpeed = defaultCircleSpeed; 67 mParallaxSpeed = PARALLAX_MEDIUM; 68 } 69 } 70 72 private void setParallax(int index) { 73 switch (index) { 74 case FAST: 75 mParallaxSpeed = PARALLAX_FAST; 76 break; 77 case MEDIUM: 78 mParallaxSpeed = PARALLAX_MEDIUM; 79 break; 80 case SLOW: 81 mParallaxSpeed = PARALLAX_SLOW; 82 break; 83 default: 84 throw new IllegalStateException("no such parallax type"); 85 } 86 } 87 88 @Override 89 protected void onDraw(Canvas canvas) { 90 super.onDraw(canvas); 91 for (int i = 0; i < CIRCLE_NUM; i++) { 92 float angle = (mCircleSpeed + mParallaxSpeed * (CIRCLE_NUM - i - 1)) * mCircleTime * 0.001f; 93 drawArc(canvas, i, angle); 94 } 95 } 96 97 private boolean mIsCircling = false; 98 99 /** 100 * 旋转开始 <功能简述> 101 */ 102 public void start() { 103 mIsCircling = true; 104 new Thread(new Runnable() { 105 106 @Override 107 public void run() { 108 mCircleTime = 0L; 109 while (mIsCircling) { 110 invalidateWrap(); 111 mCircleTime = mCircleTime + REFRESH_DURATION; 112 try { 113 Thread.sleep(REFRESH_DURATION); 114 } catch (InterruptedException e) { 115 e.printStackTrace(); 116 } 117 } 118 } 119 }).start(); 120 } 121 122 public void stop() { 123 mIsCircling = false; 124 mCircleTime = 0L; 125 invalidateWrap(); 126 } 127 128 public boolean isCircling(){ 129 return mIsCircling; 130 } 131 132 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 133 private void invalidateWrap() { 134 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 135 postInvalidateOnAnimation(); 136 } else { 137 postInvalidate(); 138 } 139 } 140 141 /** 142 * 画弧 143 * 144 * @param canvas 145 * @param index 由内而外 146 * @param startAngle 147 */ 148 private void drawArc(Canvas canvas, int index, float startAngle) { 149 Paint paint = checkArcPaint(index); 150 //最大圆是view的边界 151 RectF oval = checkRectF(calcuRadiusRatio(index)); 152 canvas.drawArc(oval, startAngle, SWEEP_ANGLE, false, paint); 153 } 154 155 private Paint mArcPaint; 156 157 private Paint checkArcPaint(int index) { 158 if (mArcPaint == null) { 159 mArcPaint = new Paint(); 160 } else { 161 mArcPaint.reset(); 162 } 163 mArcPaint.setColor(mLayerColors[index]); 164 mArcPaint.setStyle(Paint.Style.STROKE); 165 mArcPaint.setStrokeWidth(STOKE_WIDTH); 166 mArcPaint.setAntiAlias(true); 167 return mArcPaint; 168 } 169 170 private RectF mOval; 171 172 private RectF checkRectF(float radiusRatio) { 173 if (mOval == null) { 174 mOval = new RectF(); 175 } 176 float start = getMinLength() * 0.5f * (1 - radiusRatio) + STOKE_WIDTH; 177 float end = getMinLength() - start; 178 mOval.set(start, start, end, end); 179 return mOval; 180 } 181 182 private static final float RADIUS_RATIO_P = 0.2f; 183 184 /** 185 * 计算每一圈的半径比例 186 * 187 * @param index 188 * @return 189 */ 190 private float calcuRadiusRatio(int index) { 191 return 1f - (CIRCLE_NUM - index - 1) * RADIUS_RATIO_P; 192 } 193 194 private int getMinLength() { 195 return Math.min(getWidth(), getHeight()); 196 } 197 198 @Override 199 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 200 int minSize = (int) (STOKE_WIDTH * 4 * CIRCLE_NUM); 201 int wantSize = (int) (STOKE_WIDTH * 8 * CIRCLE_NUM); 202 int size = measureSize(widthMeasureSpec, wantSize, minSize); 203 setMeasuredDimension(size, size); 204 } 205 206 /** 207 * 测量view的宽高 208 * 209 * @param measureSpec 210 * @param wantSize 211 * @param minSize 212 * @return 213 */ 214 public static int measureSize(int measureSpec, int wantSize, int minSize) { 215 int result = 0; 216 int specMode = MeasureSpec.getMode(measureSpec); 217 int specSize = MeasureSpec.getSize(measureSpec); 218 219 if (specMode == MeasureSpec.EXACTLY) { 220 // 父布局想要view的大小 221 result = specSize; 222 } else { 223 result = wantSize; 224 if (specMode == MeasureSpec.AT_MOST) { 225 // wrap_content 226 result = Math.min(result, specSize); 227 } 228 } 229 //测量的尺寸和最小尺寸取大 230 return Math.max(result, minSize); 231 } 232 }
values/attrs.xml
1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3 <declare-styleable name="WhorlView_Style">
4 <attr name="WhorlView_SmallWhorlColor" format="color" />
5 <attr name="WhorlView_MiddleWhorlColor" format="color" />
6 <attr name="WhorlView_BigWhorlColor" format="color" />
7 <attr name="WhorlView_CircleSpeed" format="integer" />
8 <attr name="WhorlView_Parallax">
9 <enum name="fast" value="1" />
10 <enum name="medium" value="0" />
11 <enum name="slow" value="2" />
12 </attr>
13 </declare-styleable>
14 </resources>
以上是自定义控件代码和资源文件。下面是如何使用自定义控件。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="50dp" >
<com.dr.WhorlView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/whorl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
app:WhorlView_CircleSpeed="270"
app:WhorlView_Parallax="fast"
app:WhorlView_MiddleWhorlColor="@color/material_red"
app:WhorlView_SmallWhorlColor="@color/material_green" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/whorl"
android:layout_centerHorizontal="true"
android:layout_marginTop="18dp"
android:text="加载中..."
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
1 public class MainActivity extends Activity {
2 @Override
3 protected void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5 setContentView(R.layout.activity_main);
6 final WhorlView whorlView = (WhorlView) this.findViewById(R.id.whorl);
8 whorlView.setOnClickListener(new View.OnClickListener() {
9 @Override
10 public void onClick(View v) {
11 if (whorlView.isCircling()) {
12 whorlView.stop();
13 } else {
14 whorlView.start();
15 }
16 }
17 });
18 }
19 }
values/color.xml
1 <?xml version="1.0" encoding="utf-8"?>
2 <resources>
3 <color name="material_red">#F44336</color>
4 <color name="material_green">#4CAF50</color>
5 <color name="material_blue">#5677fc</color>
6 </resources>