又一种进度条的实现,项目结构很小,轻松移植。
包结构
运行效果:
自定义View:(伸手党只要看一些final变量的注释就能自行修改)
public class SpinnerLoader extends View {
//旋转的点的数量,默认为9(45度的情况下超过9也不显示,9以下会少点)
private static final int POINTS_COUNT = 9;
//小圆转动速度,数值越大越快
private static final int STEP = 5;
//等层圆转动速度,数值越大越快
private static final int BIG_STEP = 1;
private static final int DEFAULT_COLOR = Color.rgb(87, 247, 250);
private static final float DEFAULT_RADUIS = 180;
private static final float DEFAULT_CIRCLE_RADUIS = 40;
private static final float DEAFULT_MOVE_RADUIS = 30;
//底层圆之间的角度
private static final int SPLIT_ANGLE = 45;
//小圆与底层圆接触时的圆的大小,数值越大圆越大
private static final int ADDITION_LENGTH = 6;
private static final int FLAT_ANGLE = 180;
/**
* for save and restore instance of view.
*/
private static final String INSTANCE_STATE = "saved_instance";
private static final String ANGLE = "angle";
private static final String BIGCIRCLECENTERX = "bigCircleCenterX";
private static final String BIGCIRCLECENTERY = "bigCircleCenterY";
private static final String RADUIS = "raduis";
private static final String CIRCLERADUIS = "circleRaduis";
private static final String MOVERADUIS = "moveRaduis";
private static final String POINTCOLOR = "pointColor";
private static final String STARTX1 = "startX1";
private static final String STARTY1 = "startY1";
private static final String ENDX1 = "endX1";
private static final String ENDY1 = "endY1";
private static final String STARTX2 = "startX2";
private static final String STARTY2 = "startY2";
private static final String ENDX2 = "endX2";
private static final String ENDY2 = "endY2";
private static final String CONTROLX1 = "controlX1";
private static final String CONTROLY1 = "controlY1";
private static final String BIGSTEP = "bigStep";
private CirclePoint[] circlePoints = new CirclePoint[POINTS_COUNT];
private int angle = 0;
private int bigStep = BIG_STEP;
float bigCircleCenterX;
float bigCircleCenterY;
float raduis;
float circleRaduis;
float moveRaduis;
int pointColor;
private float startX1;
private float startY1;
private float startX2;
private float startY2;
private float controlX1;
private float controlY1;
private float endX1;
private float endY1;
private float endX2;
private float endY2;
private Path path1;
private Paint circlePaint;
private Paint linePaint;
private boolean isFirst = true;
public SpinnerLoader(Context context) {
this(context, null);
}
public SpinnerLoader(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SpinnerLoader(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SpinnerLoader,
defStyleAttr, 0);
pointColor = attributes.getColor(R.styleable.SpinnerLoader_point_color, DEFAULT_COLOR);
boolean isdynamic = attributes.getBoolean(R.styleable.SpinnerLoader_isdynamic, true);
isDynamic(isdynamic);
attributes.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
}
private int measure(int measureSpec, boolean isWidth) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
result += padding;
if (mode == MeasureSpec.AT_MOST) {
if (isWidth) {
result = Math.max(result, size);
} else {
result = Math.min(result, size);
}
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
if (isFirst) {
init();
isFirst = false;
}
for (int i = 0; i < POINTS_COUNT - 1; i ++) {
CirclePoint p = circlePoints[i];
p.x = getPaddingLeft() + bigCircleCenterX + (float)Math.cos(Math.toRadians(p.currentAngle)) * raduis;
p.y = getPaddingTop() + bigCircleCenterY + (float)Math.sin(Math.toRadians(p.currentAngle)) * raduis;
p.currentAngle = p.currentAngle + bigStep;
canvas.drawCircle(p.x, p.y, p.raduis, circlePaint);
}
calculateMoveingPoint(canvas);
angle = angle + STEP;
invalidate();
}
protected void init() {
float temp = getHeight() > getWidth() ? getWidth() / 2 : getHeight() / 2;
raduis = temp - temp / DEFAULT_RADUIS * DEFAULT_CIRCLE_RADUIS;
circleRaduis = DEFAULT_CIRCLE_RADUIS / DEFAULT_RADUIS * raduis;
moveRaduis = DEAFULT_MOVE_RADUIS / DEFAULT_RADUIS * raduis;
bigCircleCenterX = getPaddingLeft() + getWidth() / 2;
bigCircleCenterY = getPaddingTop() + getHeight() / 2;
path1 = new Path();
initializePaints();
initializePoints();
}
/**
* 计算动态的点
* @param canvas
*/
protected void calculateMoveingPoint(Canvas canvas) {
CirclePoint p = circlePoints[POINTS_COUNT - 1];
p.x = bigCircleCenterX + (float)Math.cos(Math.toRadians(angle)) * raduis;
p.y = bigCircleCenterY + (float)Math.sin(Math.toRadians(angle)) * raduis;
canvas.drawCircle(p.x, p.y, p.raduis, circlePaint);
for (int i = 0; i < POINTS_COUNT - 1; i++) {
CirclePoint biggerP1 = circlePoints[i];
//是否相交
if (isIntersect(p, biggerP1)) {
canvas.drawCircle(biggerP1.x, biggerP1.y, biggerP1.raduis + ADDITION_LENGTH*(1-getDistanceRatio(p, biggerP1)), circlePaint);
}
if (isConnect(p, biggerP1)) {
float headOffsetX1 = (float)(circleRaduis*Math.sin(Math.atan((p.y - biggerP1.y) / (p.x - biggerP1.x))));
float headOffsetY1 = (float)(circleRaduis*Math.cos(Math.atan((p.y - biggerP1.y) / (p.x - biggerP1.x))));
float footOffsetX1 = (float)(moveRaduis*Math.sin(Math.atan((p.y - biggerP1.y) / (p.x - biggerP1.x))));
float footOffsetY1 = (float)(moveRaduis*Math.cos(Math.atan((p.y - biggerP1.y) / (p.x - biggerP1.x))));
startX1 = biggerP1.x - headOffsetX1;
startY1 = biggerP1.y + headOffsetY1;
endX1 = biggerP1.x + headOffsetX1;
endY1 = biggerP1.y - headOffsetY1;
startX2 = p.x - footOffsetX1;
startY2 = p.y + footOffsetY1;
endX2 = p.x + footOffsetX1;
endY2 = p.y - footOffsetY1;
controlX1 = (biggerP1.x + p.x) / 2;
controlY1 = (biggerP1.y + p.y) / 2;
path1.reset();
path1.moveTo(startX1, startY1);
path1.quadTo(controlX1, controlY1, startX2, startY2);
path1.lineTo(endX2, endY2);
path1.quadTo(controlX1, controlY1, endX1, endY1);
path1.lineTo(startX1, startY1);
canvas.drawPath(path1, linePaint);
}
}
}
protected void initializePoints() {
for (int i = 0; i < POINTS_COUNT; i++) {
CirclePoint p = new CirclePoint();
p.currentAngle = SPLIT_ANGLE * i;
p.x = getPaddingLeft() + bigCircleCenterX + (float)Math.cos(Math.toRadians(p.currentAngle)) * raduis;
p.y = getPaddingTop() + bigCircleCenterY + (float)Math.sin(Math.toRadians(p.currentAngle)) * raduis;
p.color = pointColor;
p.raduis = circleRaduis;
if (i == POINTS_COUNT - 1) {
p.raduis = moveRaduis;
}
circlePoints[i] = p;
}
}
protected void initializePaints() {
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(pointColor);
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.FILL_AND_STROKE);
linePaint.setStrokeWidth(1);
linePaint.setColor(pointColor);
}
private boolean isIntersect(CirclePoint a, CirclePoint b) {
float distance = (float)Math.sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
return distance < (a.raduis + b.raduis);
}
private boolean isConnect(CirclePoint a, CirclePoint b) {
float distance = (float)Math.sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
return distance < raduis * Math.cos(Math.toRadians((FLAT_ANGLE - SPLIT_ANGLE) / 2));
}
private float getDistanceRatio(CirclePoint a, CirclePoint b) {
float distance = (float)Math.sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
return distance / (a.raduis + b.raduis);
}
public void setPointcolor(int color) {
pointColor = color;
if (linePaint != null) {
linePaint.setColor(color);
}
if (circlePaint != null) {
circlePaint.setColor(color);
}
}
public void isDynamic(boolean dynamic) {
if (dynamic) {
bigStep = BIG_STEP;
} else {
bigStep = 0;
}
}
protected float dp2px(float dp) {
final float scale = getResources().getDisplayMetrics().density;
return dp * scale + 0.5f;
}
@Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable(INSTANCE_STATE,super.onSaveInstanceState());
bundle.putInt(ANGLE, angle);
bundle.putFloat(BIGCIRCLECENTERX, bigCircleCenterX);
bundle.putFloat(BIGCIRCLECENTERY, bigCircleCenterY);
bundle.putFloat(RADUIS, raduis);
bundle.putFloat(CIRCLERADUIS, circleRaduis);
bundle.putFloat(MOVERADUIS, moveRaduis);
bundle.putFloat(STARTX1, startX1);
bundle.putFloat(STARTY1, startY1);
bundle.putFloat(ENDX1, endX1);
bundle.putFloat(ENDY1, endY1);
bundle.putFloat(STARTX2, startX2);
bundle.putFloat(STARTY2, startY2);
bundle.putFloat(ENDX2, endX2);
bundle.putFloat(ENDY2, endY2);
bundle.putFloat(CONTROLX1, controlX1);
bundle.putFloat(CONTROLY1, controlY1);
bundle.putInt(POINTCOLOR, pointColor);
bundle.putInt(BIGSTEP, bigStep);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if(state instanceof Bundle){
final Bundle bundle = (Bundle)state;
angle = bundle.getInt(ANGLE);
bigCircleCenterX = bundle.getFloat(BIGCIRCLECENTERX);
bigCircleCenterY = bundle.getFloat(BIGCIRCLECENTERY);
raduis = bundle.getFloat(RADUIS);
circleRaduis = bundle.getFloat(CIRCLERADUIS);
moveRaduis = bundle.getFloat(MOVERADUIS);
startX1 = bundle.getFloat(STARTX1);
startY1 = bundle.getFloat(STARTY1);
endX1 = bundle.getFloat(ENDX1);
endY1 = bundle.getFloat(ENDY1);
startX2 = bundle.getFloat(STARTX2);
startY2 = bundle.getFloat(STARTY2);
endX2 = bundle.getFloat(ENDX2);
endY2 = bundle.getFloat(ENDY2);
controlX1 = bundle.getFloat(CONTROLX1);
controlY1 = bundle.getFloat(CONTROLY1);
pointColor = bundle.getInt(POINTCOLOR);
bigStep = bundle.getInt(BIGSTEP);
init();
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE));
return;
}
super.onRestoreInstanceState(state);
}
static class CirclePoint {
public int currentAngle;
public float raduis;
public float x;
public float y;
public int color;
}
}
MainActivity(可以无视)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<customdialog.wjj.com.customdialog.SpinnerLoader
android:id="@+id/one"
android:layout_marginTop="30dp"
android:layout_centerHorizontal="true"
app:point_color="#FF6347"
app:isdynamic="false"
android:layout_width="40dp"
android:layout_height="40dp"/>
<customdialog.wjj.com.customdialog.SpinnerLoader
android:id="@+id/two"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/one"
android:layout_width="90dp"
android:layout_height="90dp"
app:point_color="#FF00FF"
/>
<customdialog.wjj.com.customdialog.SpinnerLoader
android:id="@+id/three"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:layout_below="@+id/two"
android:layout_width="120dp"
android:layout_height="120dp"
app:point_color="#D2691E"
/>
</RelativeLayout>
所用到的attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SpinnerLoader">
<attr name="point_color" format="color"/>
<attr name="isdynamic" format="boolean"/>
</declare-styleable>
</resources>
伸手党们可以下载就用,简单粗暴,为了生活
源码地址:http://yunpan.cn/cdUai7ndmFhHQ 访问密码 9491
android 自定义View SpinnerLoader使用解析,让你摆脱系统难看的进度条
原文地址:http://blog.csdn.net/ddwhan0123/article/details/47776539