前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android-打造一个简单通用的Material加载LoadingView

Android-打造一个简单通用的Material加载LoadingView

作者头像
三好码农
发布2018-09-11 10:54:47
2.9K0
发布2018-09-11 10:54:47
举报
文章被收录于专栏:三好码农的三亩自留地

我们开发App时,都难免要向服务器请求数据,在数据返回之前一般都需要有个进度指示器来告诉用户,程序正在拼命帮你加载,当数据返回后展示正常数据,这是个很简单也很常用的功能,但是可能每一个页面都需要为这个简单功能浪费精力体力,所以我们需要一个简单通用的加载LoadingView。

实现Material Progressbar

因为网络请求的时间一般是未知的,所以我们一般都是用一个循环的圆圈指示器来提示用户,如下图。

Material-Progressbar

这个View,仔细观察,可以按下面的步骤做无限循环来显示:

1.根据起始弧度startArc和要画的弧度arc,画一个弧形,弧度arc逐渐加大。 2.判读弧度arc是否大于maxArc,如果为真,起始弧度startArc开始增加,弧度arc逐渐减少。 3.当弧度arc小于minArc时,回到第1步。 同时,整个画布canvas在按照一个角速度做旋转。除此之外还有一件事情要做,需要在弧形中间画一个圆形,来擦除中间部分的颜色,我们可以用Xfermode来实现,Xfermode可以对多个图层按规则进行混合,具体可以自行Google哦。

我们开始动手实现,篇幅关系,只贴一些关键代码片段(项目已经共享到Github,结尾会给出链接)。

代码语言:javascript
复制
public class MaterialCircleView extends View {

/**
 * 是否需要对画笔颜色进行渐变处理
 */
private boolean bGradient;
/**
 * 画笔颜色
 */
private int circleColor;
/**
 * 画圆圈宽度
 */
private int circleWidth;
/**
 * 圆圈半径
 */
private int radius;
public MaterialCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray t = null;
    try {
        t = context.obtainStyledAttributes(attrs, R.styleable.MaterialCircleView,
                0, defStyleAttr);
        setbGradient(t.getBoolean(R.styleable.MaterialCircleView_bGradient, true));
        circleColor = t.getColor(R.styleable.MaterialCircleView_circleColor,
                getResources().getColor(android.R.color.holo_blue_light));
        circleWidth = t.getDimensionPixelSize(R.styleable.MaterialCircleView_circleWidth,
                10);
        radius = t.getDimensionPixelSize(R.styleable.MaterialCircleView_radius,
                50);
    } finally {
        if (t != null) {
            t.recycle();
        }
    }

    mPaint = new Paint();
    if (isbGradient()) {
        mPaint.setColor(Color.rgb(red, green, blue));
    }else {
        mPaint.setColor(circleColor);
    }
    mPaint.setAntiAlias(true);
    setBackgroundColor(getResources().getColor(android.R.color.transparent));
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    sWidth = this.getMeasuredWidth();
    sHeight = this.getMeasuredHeight();
    halfWidth = sWidth / 2;
    halfHeight = sHeight / 2;
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //计算startAngle和endAngle,
    //保证它们在maxAngle和minAngle之间循环递增递减
    if (startAngle == minAngle) {
        endAngle += 6;
    }
    if (endAngle >= 280 || startAngle > minAngle) {
        startAngle += 6;
        if(endAngle > 20) {
            endAngle -= 6;
        }

    }
    if (startAngle > minAngle + 280) {
        minAngle = startAngle;
        startAngle = minAngle;
        endAngle = 20;
    }

    checkPaint();
    //旋转canvas
    canvas.rotate(curAngle += rotateDelta, halfWidth, halfHeight);
    //将弧度和擦除圆形绘制在bitmap上
    Bitmap bitmap = Bitmap.createBitmap(sWidth, sHeight, Bitmap.Config.ARGB_8888);
    Canvas bmpCanvas = new Canvas(bitmap);
    bmpCanvas.drawArc(new RectF(0, 0, sWidth, sHeight), startAngle, endAngle, true, mPaint);
    Paint transparentPaint = new Paint();
    transparentPaint.setAntiAlias(true);
    transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
    transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    bmpCanvas.drawCircle(halfWidth, halfHeight,
            halfWidth - circleWidth, transparentPaint);
    canvas.drawBitmap(bitmap, 0, 0, new Paint());
    //保证绘制动画延续
    invalidate();
}

整个实现过程就是这样,代码量比较少,这里顺带提一下,我们额外实现了一个颜色渐变的过程,R.styleable.MaterialCircleView_bGradient属性是true时启用,其实就一直改变mPaint的颜色。

代码语言:javascript
复制
private int colorDelta = 2;
private void checkPaint() {
    if (isbGradient()) {
        switch (phase % 5) {
            case 0:
                green += colorDelta;
                if (green > 255) {
                    green = 255;
                    phase ++;
                }
                break;
            case 1:
                red += colorDelta;
                green -= colorDelta;
                if (red > 255) {
                    red = 255;
                    green = 0;
                    phase ++;
                }
                break;
            case 2:
                blue -= colorDelta;
                if (blue < 0) {
                    blue = 0;
                    phase ++;
                }
                break;
            case 3:
                red -= colorDelta;
                green += colorDelta;
                if (red < 0) {
                    red = 0;
                    green = 255;
                    phase ++;
                }
                break;
            case 4:
                green -= colorDelta;
                blue += colorDelta;
                if (green < 0) {
                    green = 0;
                    blue = 255;
                    phase ++;
                }
                break;
        }
        mPaint.setColor(Color.rgb(red, green, blue));
    }
}

实现UniversalLoadingView

现在已经有了圆形指示器,还需要一个textView来显示文字,所以我们再封装一个ViewGroup,来管理加载的几种状态,包括指示器的隐藏和现实,textView文本的改变等。同样只贴关键代码片段。

代码语言:javascript
复制
public class UniversalLoadingView extends ViewGroup{
public enum State{
    GONE,
    LOADING,
    LOADING_FALIED,
    LOADING_EMPTY
}

public UniversalLoadingView(Context context) {
    this(context, null);
}

public UniversalLoadingView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public UniversalLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray t = null;
    try {
        t = context.obtainStyledAttributes(attrs, R.styleable.MaterialCircleView,
                0, defStyleAttr);
        bGradient = t.getBoolean(R.styleable.MaterialCircleView_bGradient, true);
        circleColor = t.getColor(R.styleable.MaterialCircleView_circleColor,
                getResources().getColor(android.R.color.holo_blue_light));
        circleWidth = t.getDimensionPixelSize(R.styleable.MaterialCircleView_circleWidth,
                10);
        radius = t.getDimensionPixelSize(R.styleable.MaterialCircleView_radius,
                MaterialCircleView.dpToPx(50, getResources()));
    } finally {
        if (t != null) {
            t.recycle();
        }
    }

    try {
        t = context.obtainStyledAttributes(attrs, R.styleable.UniversalLoadingView,
                0, defStyleAttr);
        setbTransparent(t.getBoolean(R.styleable.UniversalLoadingView_bg_transparent, false));
        alpha = t.getDimensionPixelSize(R.styleable.UniversalLoadingView_bg_alpha,
                255);
    } finally {
        if (t != null) {
            t.recycle();
        }
    }

    materialCircleView = new MaterialCircleView(context, attrs, defStyleAttr);
   //add circle view
    addView(materialCircleView);

    mTipTextView = new TextView(context);
    mTipTextView.setText(LOADING_TIP);
    mTipTextView.setTextSize(16f);
    mTipTextView.setGravity(Gravity.CENTER);
    mTipTextView.setSingleLine(false);
    mTipTextView.setMaxLines(2);
    mTipTextView.setTextColor(getResources().getColo r(android.R.color.darker_gray));
    addView(mTipTextView);

    this.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            if (mLoadState == State.LOADING_EMPTY || mLoadState == State.LOADING_FALIED) {
                if (mReloadListener != null) {
                    mReloadListener.reload();
                }
            }
        }
    });

    mHandler = new Handler();

    if (isbTransparent()) {
        setBackgroundColor(getResources().getColor(android.R.color.transparent));
    }
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//        return super.onInterceptTouchEvent(ev);
    return true;
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    LayoutParams params = (LayoutParams) materialCircleView.getLayoutParams();
    sWidth = MeasureSpec.getSize(widthMeasureSpec);
    sHeight = MeasureSpec.getSize(heightMeasureSpec);
    params.left = (sWidth - radius) / 2;
    params.top = (sHeight - radius) / 2 - radius;
    params.width = radius;
    params.height = radius;

    LayoutParams tipParams = (LayoutParams) mTipTextView.getLayoutParams();
    int tipWidth = MaterialCircleView.dpToPx(100, getResources());
    int tipHeight = MaterialCircleView.dpToPx(50, getResources());
    tipParams.left = (sWidth - tipWidth) / 2;
    tipParams.top = (sHeight - radius) / 2 ;
    tipParams.width = tipWidth;
    tipParams.height = tipHeight;

    setMeasuredDimension(sWidth, sHeight);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    LayoutParams params = (LayoutParams) materialCircleView.getLayoutParams();
    materialCircleView.layout(params.left, params.top, params.left + params.width
                , params.top + params.height);
    LayoutParams tipParams = (LayoutParams) mTipTextView.getLayoutParams();
    mTipTextView.layout(tipParams.left, tipParams.top, tipParams.left + tipParams.width,
            tipParams.top + tipParams.height);
}

我们还需要一个暴露一个重试加载数据的接口,因为总有网络不好的时候。

代码语言:javascript
复制
public void setOnReloadListener(ReloadListner listener) {
    this.mReloadListener = listener;
}

/**
 * reload interface
 */
public interface ReloadListner {
    public void reload();
}

在Activity的Xml布局文件中,我们可以直接添加

代码语言:javascript
复制
  <com.sw.library.widget.library.UniversalLoadingView
    android:id="@+id/loadingView"
    app:bGradient="false"
    app:radius="50dp"
    app:bg_transparent="false"
    app:circleColor="@android:color/holo_green_dark"
    android:background="@android:color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></com.sw.library.widget.library.UniversalLoadingView>

也可以直接new UniversalLoadingView来创建,然后addView到布局根容器中。 这个项目我已经共享到Github了 https://github.com/aliouswang/UniversalLoadingView 现在功能还比较弱,还有很多地方可以改进,欢迎大家pull request,共同进步. 最后是运行效果图,有图有真相。

demo.gif

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015.05.07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现Material Progressbar
  • 实现UniversalLoadingView
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档