android 自定义View SpinnerLoader使用解析,让你摆脱系统难看的进度条

转载请注明出处王亟亟的大牛之路

又一种进度条的实现,项目结构很小,轻松移植。

包结构

运行效果:

自定义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

时间: 2024-08-29 11:36:34

android 自定义View SpinnerLoader使用解析,让你摆脱系统难看的进度条的相关文章

我的Android进阶之旅------&gt;Android自定义View来实现解析lrc歌词并同步滚动、上下拖动、缩放歌词的功能

前言 一LRC歌词文件简介 1什么是LRC歌词文件 2LRC歌词文件的格式 LRC歌词文件的标签类型 1标识标签 2时间标签 二解析LRC歌词 1读取出歌词文件 2解析得到的歌词内容 1表示每行歌词内容的实体类LrcRow 2解析歌词的构造器 ILrcBuilder接口 DefaultLrcBuilder歌词解析构造器 lrc歌词原始内容 lrc歌词解析后的内容 三显示LRC歌词内容 1定义一个ILrcViewListener接口 2定义一个ILrcView接口 3自定义一个LrcView 同步

Android自定义View之Path解析

马上要到五一了,提前祝贺大家五一快乐. 今天的主角是路劲,这个词给我的第一印象就是那种弯弯的小路,届时让我想起了小时候的曲径通幽处,反正我是忘记了下一句是什么.言归正传,Android中的Path又有哪一些呢,以及它们的常用方法. Path常用方法 方法 作用 备注 moveTo 移动起点 移动下一次操作的起点位置 lineTo 连接直线 连接上一个点到当前点之间的直线 setLastPoint 设置终点 重置最后一个点的位置 close 闭合路劲 从最后一个点连接最初的一个点,形成一个闭合区域

Android自定义View(二、深入解析自定义属性)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 属性值的类型format 类中获取属性值 Attributeset和TypedArray以及declare-styleable ??在上一篇博客<Android自定义View(一.初体验)>中我们体验了自定义控件的基本流程: 继承View,覆盖构造方法 自定义属性 重写onMeasure方法测量宽

Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 onMeasure方法执行流程 MeasureSpec类 从ViewGroup的onMeasure到View的onMeasure ViewGroup中三个测量子控件的方法 getChildMeasureSpec方法 View的onMeasure setMeasuredDimension ??在上一篇

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5

Android自定义View(RollWeekView-炫酷的星期日期选择控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义CustomWeekView 4重写onMeasure 5点击后执行动画 7重置预备控件 源码下载 ??最近收到一个自定义控件的需求,需要做一个日期选择控件,实现图如下: ???? ??一次展示一个星期的5天,中间放大的为当前选中的:如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大

Android自定义View(LineBreakLayout-自动换行的标签容器)

??最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了.而自定义控件系列文章中对于自定义ViewGroup上次只是讲解了一些基础和步骤 Android自定义ViewGroup(四.打造自己的布局容器),这次就着这个例子我们来完成一个能在项目中使用的自定义布局容器. 1. 初步分析 ??首先我们看一看要完成的效果图: ?????? ??上面红色标示出的就

android 自定义view 前的基础知识

本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) Android视图状态及重绘流程分析,带你一步步深入了解View(三) Android自定义View的实现方法,带你一步步深入了解View(四) 这位大哥的系列博文,相当于自己看这些的一个思考吧. 一.首先学layoutInflater. 相信接触Android久一点的朋友对于La