且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Android 的自定义等待对话框

更新时间:2022-07-06 20:50:21

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010046908/article/details/50614383


      最近,看了好多的APP的等待对话框,发现自己的太lower,于是就研究了一番,最后经过苦心努力,实现一个。

  1. 自定义一个LoadingIndicatorView(extends View )类
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中LoadingIndicatorView使用自定义的属性(注意namespace)
  4. 在LoadingIndicatorView的构造方法中通过TypedArray获取
   描述就提供这些,一下是代码的展示,非常的详细。
  • 自定义属性的声明文件
<declare-styleable name="AVLoadingIndicatorView">
        <attr name="indicator">
            <flag name="BallSpinFadeLoader" value="22"/>
        </attr>
        <attr name="indicator_color" format="color"/>
    </declare-styleable>

<pre name="code" class="html">LoadingIndicatorView.java

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.View;

import com.chni.lidong.androidtestdemo.R;


/**
 * Created by lidongon 2016/1/31
 *
 .BallSpinFadeLoader,
 *
 */
public class LoadingIndicatorView extends View {


    //indicators 指示器
    public static final int BallSpinFadeLoader=22;

    @IntDef(flag = true,
            value = {
                    BallSpinFadeLoader,
            })
    public @interface Indicator{}

    //Sizes (with defaults in DP)
    public static final int DEFAULT_SIZE=45;

    //attrs
    int mIndicatorId;
    int mIndicatorColor;

    Paint mPaint;

    BaseIndicatorController mIndicatorController;

    private boolean mHasAnimation;


    public LoadingIndicatorView(Context context) {
        super(context);
        init(null, 0);
    }

    public LoadingIndicatorView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr);
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr);
    }

    private void init(AttributeSet attrs, int defStyle) {
        /**
         *获取TypedArray(属性的集合)
         */
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.AVLoadingIndicatorView);
        mIndicatorId=a.getInt(R.styleable.AVLoadingIndicatorView_indicator, BallSpinFadeLoader);//获取编号属性
        mIndicatorColor=a.getColor(R.styleable.AVLoadingIndicatorView_indicator_color, Color.WHITE);//获取颜色属性
        a.recycle();//回收属性的集合
        mPaint=new Paint();
        mPaint.setColor(mIndicatorColor);//设置画笔的颜色
        mPaint.setStyle(Paint.Style.FILL);//设置画笔的样式为填充
        mPaint.setAntiAlias(true);//去锯齿
        applyIndicator();//
    }

    private void applyIndicator(){
        switch (mIndicatorId){
            case BallSpinFadeLoader:
                mIndicatorController=new BallSpinFadeLoaderIndicator();
                break;
        }
        mIndicatorController.setTarget(this);//将控件设置到当前View
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width  = measureDimension(dp2px(DEFAULT_SIZE), widthMeasureSpec);//获取View的宽度
        int height = measureDimension(dp2px(DEFAULT_SIZE), heightMeasureSpec);//获取View的高度
        setMeasuredDimension(width, height);//
    }

    /**
     *测量的 维度
     * @param defaultSize 默认大小
     * @param measureSpec {@see widthMeasureSpec,heightMeasureSpec}
     * @return 返回测量的结果
     */
    private int measureDimension(int defaultSize,int measureSpec){
        int result = defaultSize;
        int specMode = MeasureSpec.getMode(measureSpec);//测量规范
        int specSize = MeasureSpec.getSize(measureSpec);//测量大小
        if (specMode == MeasureSpec.EXACTLY) {//父控件已经为子控件设置确定的大小,子控件会考虑父控件给他的大小,自己需要多大设置多大
            result = specSize;
        } else if (specMode == MeasureSpec.AT_MOST) {//子控件可以设置自己希望的指定大小
            result = Math.min(defaultSize, specSize);//取最小值
        } else {
            result = defaultSize;
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawIndicator(canvas);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!mHasAnimation){
            mHasAnimation=true;
            applyAnimation();
        }
    }

    void drawIndicator(Canvas canvas){
        mIndicatorController.draw(canvas,mPaint);
    }

    void applyAnimation(){
        mIndicatorController.createAnimation();
    }

    private int dp2px(int dpValue) {
        return (int) getContext().getResources().getDisplayMetrics().density * dpValue;
    }
BaseIndicatorController.java 
package com.chni.lidong.androidtestdemo.loading;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

/**
 * Created by lidongon 2016/1/31
 */
public abstract class BaseIndicatorController {

    private View mTarget;


    public void setTarget(View target){
        this.mTarget=target;
    }

    public View getTarget(){
        return mTarget;
    }

    /**
     * 得到View的宽度
     * @return
     */
    public int getWidth(){
        return mTarget.getWidth();
    }

    /**
     * 得到view的高度
     * @return
     */
    public int getHeight(){
        return mTarget.getHeight();
    }

    /**
     * 刷新view
     */
    public void postInvalidate(){
        mTarget.postInvalidate();
    }

    /**
     * draw indicator what ever
     * you want to draw
     * 绘制indicate
     * @param canvas
     * @param paint
     */
    public abstract void draw(Canvas canvas,Paint paint);

    /**
     * create animation or animations
     * ,and add to your indicator.
     * 创建动画或者动画集合,添加到indcator
     */
    public abstract void createAnimation();


}

 BallSpinFadeLoaderIndicator.java

package com.chni.lidong.androidtestdemo.loading;

import android.graphics.Canvas;
import android.graphics.Paint;

import com.nineoldandroids.animation.ValueAnimator;

/**
 * Created by lidongon 2016/1/31
 */
public class BallSpinFadeLoaderIndicator extends BaseIndicatorController {

    public static final float SCALE=1.0f;

    public static final int ALPHA=255;
    /**
     * 圆点的比例
     */
    float[] scaleFloats=new float[]{SCALE,
            SCALE,
            SCALE,
            SCALE,
            SCALE,
            SCALE,
            SCALE,
            SCALE};
    /**
     * 圆点的透明度集合
     */
    int[] alphas=new int[]{ALPHA,
            ALPHA,
            ALPHA,
            ALPHA,
            ALPHA,
            ALPHA,
            ALPHA,
            ALPHA};


    @Override
    public void draw(Canvas canvas, Paint paint) {
        float radius=getWidth()/10;
        for (int i = 0; i < 8; i++) {
            canvas.save();
            Point point=circleAt(getWidth(),getHeight(),getWidth()/2-radius,i*(Math.PI/4));
            canvas.translate(point.x,point.y);
            canvas.scale(scaleFloats[i],scaleFloats[i]);
            paint.setAlpha(alphas[i]);
            canvas.drawCircle(0,0,radius,paint);
            canvas.restore();
        }
    }

    /**
     * 圆O的圆心为(a,b),半径为R,点A与到X轴的为角α.
     *则点A的坐标为(a+R*cosα,b+R*sinα)
     * @param width
     * @param height
     * @param radius
     * @param angle
     * @return
     */
    Point circleAt(int width,int height,float radius,double angle){
        float x= (float) (width/2+radius*(Math.cos(angle)));
        float y= (float) (height/2+radius*(Math.sin(angle)));
        return new Point(x,y);
    }

    @Override
    public void createAnimation() {
        int[] delays= {0, 120, 240, 360, 480, 600, 720, 780, 840};
        for (int i = 0; i < 8; i++) {
            final int index=i;
            ValueAnimator scaleAnim=ValueAnimator.ofFloat(1,0.4f,1);//创建ValueAnimator对象
            scaleAnim.setDuration(1000);//设置动画的持续时间
            scaleAnim.setRepeatCount(-1);//设置动画是否重复
            scaleAnim.setStartDelay(delays[i]);//延迟启动动画
            scaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//ValueAnimator只负责第一次的内容,因此必须通过监听来实现对象的相关属性的更新
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    scaleFloats[index] = (float) animation.getAnimatedValue();//获取当前帧的值
                    postInvalidate();
                }
            });
            scaleAnim.start();//启动属性动画

            ValueAnimator alphaAnim=ValueAnimator.ofInt(255, 77, 255);//透明度动画
            alphaAnim.setDuration(1000);//
            alphaAnim.setRepeatCount(-1);
            alphaAnim.setStartDelay(delays[i]);
            alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    alphas[index] = (int) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
            alphaAnim.start();
        }
    }

    final class Point{
        public float x;
        public float y;

        public Point(float x, float y){
            this.x=x;
            this.y=y;
        }
    }


}

UIHelp.java
package com.chni.lidong.androidtestdemo.utils;

import android.app.Activity;
import android.app.Dialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.chni.lidong.androidtestdemo.R;

/**
 * 对话框的实现
 * @author 李东
 * @Date 2014-11-23
 */
public class UIHelper {
	
	/** 加载数据对话框 */
	private static Dialog mLoadingDialog;
	
	
	/**
	 * 显示加载对话框
	 * @param context 上下文
	 * @param msg 对话框显示内容
	 * @param cancelable 对话框是否可以取消
	 */
	public static void showDialogForLoading(Activity context, String msg, boolean cancelable) {
		View view = LayoutInflater.from(context).inflate(R.layout.layout_loading_dialog, null);
		TextView loadingText = (TextView)view.findViewById(R.id.id_tv_loading_dialog_text);
		loadingText.setText(msg);
		
		mLoadingDialog = new Dialog(context, R.style.loading_dialog_style);
		mLoadingDialog.setCancelable(cancelable);
		mLoadingDialog.setContentView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
		mLoadingDialog.show();		
	}
	
	/**
	 * 关闭加载对话框
	 */
	public static void hideDialogForLoading() {
		if(mLoadingDialog != null && mLoadingDialog.isShowing()) {
			mLoadingDialog.cancel();
		}
	}

}

对话框的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_loading_dialog_shape"
    android:gravity="center"
    android:minHeight="60dp"
    android:minWidth="180dp"
    android:orientation="vertical"
    android:padding="@dimen/padding_10" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:layout_height="wrap_content">

        <com.chni.lidong.androidtestdemo.loading.AVLoadingIndicatorView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:indicator="BallSpinFadeLoader"
            app:indicator_color="@color/green"
            />

    </LinearLayout>

    <TextView
        android:id="@+id/id_tv_loading_dialog_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/padding_5"
        android:text="正在登录…"
        android:textColor="@color/content"
        android:textSize="14sp" />

</LinearLayout>

对话框的样式:

<!-- 自定义Loading Dialog -->
<style name="loading_dialog_style" parent="@android:style/Theme.Dialog">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowBackground">@color/transparent</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>


MainActivity.java

public class Main7Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main7);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
        UIHelper.showDialogForLoading(this, "正在加载...", true);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                UIHelper.hideDialogForLoading();
            }
        },10000);
    }

}


最终的截图:

Android 的自定义等待对话框