开源项目android-process-button使用和源码分析

本文介绍了开源项目 android-process-button 的使用,包括如何引用项目及其核心类FlatButton和ActionProcessButton的详细分析。 FlatButton 是 Button 的简单扩展,而 ActionProcessButton 则实现了进度显示效果,通过drawProgress()方法实现视觉变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

android-process-button,扁平化带进度显示的按钮,来自Google的开发专家dmytrodanylyk的作品。

显示效果如图所示。


项目地址:
https://github.com/dmytrodanylyk/android-process-button

其中包含项目源码和Demo。

一、项目使用

(1).项目的引用

dependencies {
    compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.0'
}
(2).自定义属性的介绍

<resources>
    <declare-styleable name="ProcessButton">
        <!-- “加载中”显示的文字 -->
        <attr name="pb_textProgress" format="string" />
        <!-- “加载完成”显示的文字 -->
        <attr name="pb_textComplete" format="string" />
        <!-- “加载出错”显示的文字 -->
        <attr name="pb_textError" format="string" />
        <!-- “加载中”进度条背景 -->
        <attr name="pb_colorProgress" format="color" />
        <!-- “加载完成”进度条背景 -->
        <attr name="pb_colorComplete" format="color" />
        <!-- “加载出错”进度条背景 -->
        <attr name="pb_colorError" format="color" />
    </declare-styleable>

    <declare-styleable name="FlatButton">
        <!-- 按钮按下后的背景色 -->
        <attr name="pb_colorPressed" format="color" />
        <!-- 按钮默认的背景色 -->
        <attr name="pb_colorNormal" format="color" />
        <!-- 按钮的圆角半径 -->
        <attr name="pb_cornerRadius" format="dimension" />
    </declare-styleable>
</resources>

二、源码分析
(1).类的概要
1. FlatButton,继承自Button。处理按钮的基本样式,包括背景,圆角和文字。
2. ProcessButton,继承自FlatButton。处理进度相关的显示,包括进度条样式、与进度相关的文字,并提供drawProgress()抽象方法由子类去实现。
3.剩下的三个类, ActionProcessButtonSubmitProcessButtonGenerateProcessButton,都继承自ProcessButton。各自实现了drawProgress()方法,绘制不同样式的进度条。

类的继承图:


(2).类的详解之基础类FlatButton

先来分析最基础的类FlatButton,该类较简单,主要是在构造器内调用init()方法进行初始化。
从类的定义可以看出,它继承自系统的Button类。

public class FlatButton extends Button {
}
成员变量,定义了背景、文字和圆角半径。
// 背景
private StateListDrawable mNormalDrawable;
// 文字
private CharSequence mNormalText;
// 圆角半径
private float cornerRadius;
三个成员变量都支持自定义设置。在项目内部提供了rect_normal.xml文件,作为按钮的默认显示背景。rect_normal.xml是一个layer-list类型drawable文件,内部包含2个item。然后在java代码中将其加载为LayerDrawable对象,取出每一个item,再根据用户设置的颜色和圆角对其进行修改。成员变量mNormalDrawable是StateListDrawable类型对象,这里采用动态代码添加各种状态,包括state_pressed、state_focused、state_selected和默认状态。
// 初始化cornerRadius和mNormalDrawable
private void initAttributes(Context context, AttributeSet attributeSet) {
    TypedArray attr = getTypedArray(context, attributeSet, R.styleable.FlatButton);
    if (attr == null) {
        return;
    }

    try {
        float defValue = getDimension(R.dimen.corner_radius);
        // 可以通过自定义属性pb_cornerRadius修改圆角半径
        cornerRadius = attr.getDimension(R.styleable.FlatButton_pb_cornerRadius, defValue);

        // mNormalDrawable是StateListDrawable类型对象,这里采用动态代码添加各种状态,包括state_pressed、state_focused、state_selected和默认
        mNormalDrawable.addState(new int[]{android.R.attr.state_pressed}, createPressedDrawable(attr));
        mNormalDrawable.addState(new int[]{android.R.attr.state_focused}, createPressedDrawable(attr));
        mNormalDrawable.addState(new int[]{android.R.attr.state_selected}, createPressedDrawable(attr));
        mNormalDrawable.addState(new int[]{}, createNormalDrawable(attr));
    } finally {
        attr.recycle();
    }
}

// 创建默认的按钮背景
private LayerDrawable createNormalDrawable(TypedArray attr) {
    // rect_normal是一个layer-list类型drawable文件,内部包含2个item
    LayerDrawable drawableNormal = (LayerDrawable) getDrawable(R.drawable.rect_normal).mutate();

    // 可以通过修改cornerRadius成员变量来改变圆角半径
    GradientDrawable drawableTop = (GradientDrawable) drawableNormal.getDrawable(0).mutate();
    drawableTop.setCornerRadius(getCornerRadius());

    // 按钮按下之后的颜色,默认深蓝色,可通过pb_colorPressed自定义属性修改
    int blueDark = getColor(R.color.blue_pressed);
    int colorPressed = attr.getColor(R.styleable.FlatButton_pb_colorPressed, blueDark);
    drawableTop.setColor(colorPressed);

    GradientDrawable drawableBottom = (GradientDrawable) drawableNormal.getDrawable(1).mutate();
    drawableBottom.setCornerRadius(getCornerRadius());

    // 按钮默认的颜色,默认浅蓝色,可通过pb_colorNormal自定义属性修改
    int blueNormal = getColor(R.color.blue_normal);
    int colorNormal = attr.getColor(R.styleable.FlatButton_pb_colorNormal, blueNormal);
    drawableBottom.setColor(colorNormal);

    return drawableNormal;
}

// 创建获取到焦点后的按钮背景
private Drawable createPressedDrawable(TypedArray attr) {
    GradientDrawable drawablePressed = (GradientDrawable) getDrawable(R.drawable.rect_pressed).mutate();
    drawablePressed.setCornerRadius(getCornerRadius());

    int blueDark = getColor(R.color.blue_pressed);
    int colorPressed = attr.getColor(R.styleable.FlatButton_pb_colorPressed, blueDark);
    drawablePressed.setColor(colorPressed);

    return drawablePressed;
}
使用到的rect_normal.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <corners android:radius="@dimen/corner_radius" />
            <solid android:color="@color/blue_pressed" />
        </shape>
    </item>

    <item android:bottom="@dimen/layer_padding">
        <shape android:shape="rectangle">
            <corners android:radius="@dimen/corner_radius" />
            <solid android:color="@color/blue_normal" />
        </shape>
    </item>
</layer-list>
有了Drawable类型对象后,需要设置为按钮背景。在调用Button类的setBackground方法时,这里有两个小细节。一,兼容性,Android在4.1之前使用的是setBackgroundDrawable()方法,4.1之后才有了setBackground()方法;二,如果为按钮添加了内间距padding需要注意,由于Button在调用setBackgroundXXX(drawable)方法时,会使用参数drawable的padding值,Button的padding值会失效,所以需要先将padding值保存起来,在setBackgroundXXX()方法被调用之后,再设置Button的padding。

// 设置按钮的背景
// 分两步:(1).setBackground(),(2).setPadding()
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
public void setBackgroundCompat(Drawable drawable) {
    // 因为在调用setBackgroundXXX()方法时,会使用参数drawable的padding值,原View的padding值失效
    // 所以这里先将padding值保存起来,在setBackgroundXXX()方法被调用之后,再设置View的padding
    int pL = getPaddingLeft();
    int pT = getPaddingTop();
    int pR = getPaddingRight();
    int pB = getPaddingBottom();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        setBackground(drawable);
    } else {
        setBackgroundDrawable(drawable);
    }
    setPadding(pL, pT, pR, pB);
}
准备工作完毕,接下来需要在构造方法中执行了。
public FlatButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    // 创建mNormalDrawable
    mNormalDrawable = new StateListDrawable();
    if (attrs != null) {
        initAttributes(context, attrs);
    }
    // 取按钮中的文字
    mNormalText = getText().toString();
    // 设置mNormalDrawable为按钮背景
    setBackgroundCompat(mNormalDrawable);
}
(3).类的详解之基础类ProcessButton
ProcessButton是一个抽象类,继承自FlatButton。FlatButton已经完成了按钮的基本样式显示,ProcessButton就只专心负责进度条了。从类的定义和成员变量很明显可以看出来。
public abstract class ProcessButton extends FlatButton {
    // 当前进度
    private int mProgress;
    // 最大进度
    private int mMaxProgress;
    // 最小进度
    private int mMinProgress;

    // “加载中”进度条背景
    private GradientDrawable mProgressDrawable;
    // “加载完成”进度条背景
    private GradientDrawable mCompleteDrawable;
    // “加载出错”进度条背景
    private GradientDrawable mErrorDrawable;

    // “加载中”显示的文字
    private CharSequence mLoadingText;
    // “加载完成”显示的文字
    private CharSequence mCompleteText;
    // “加载出错”显示的文字
    private CharSequence mErrorText;
}
接下来成员变量在构造方法中完成初始化,支持用户自定义设置属性,当没有设置时会取默认值。这些与FlatButton如出一辙,不再累述。

这里有两个核心方法需要说明。一个是onDraw()方法,对onDraw()重写,在方法内部实现进度条的绘制。但是ProcessButton是一个抽象类,并不知道按钮的具体显示样式,所以提供了抽象方法drawProgress()由子类去实现。
@Override
protected void onDraw(Canvas canvas) {
    // 绘制自定义进度条
    if (mProgress > mMinProgress && mProgress < mMaxProgress) {
        drawProgress(canvas);
    }

    // super.onDraw()一定要调用,绘制按钮自身
    super.onDraw(canvas);
}

public abstract void drawProgress(Canvas canvas);
另一个方法是setProgress(int progress),根据传入的参数progress来更新按钮的显示,包括进度条的样式和文字。
// 根据当前进度,刷新按钮的进度条和文字,最后一定要调用invalidate()
public void setProgress(int progress) {
    mProgress = progress;

    if (mProgress == mMinProgress) {
        onNormalState();
    } else if (mProgress == mMaxProgress) {
        onCompleteState();
    } else if (mProgress < mMinProgress) {
        onErrorState();
    } else {
        onProgress();
    }

    invalidate();
}

// 显示为:出错了
protected void onErrorState() {
    if (getErrorText() != null) {
        setText(getErrorText());
    }
    setBackgroundCompat(getErrorDrawable());
}

// 显示为:加载中
protected void onProgress() {
    if (getLoadingText() != null) {
        setText(getLoadingText());
    }
    setBackgroundCompat(getNormalDrawable());
}

// 显示为:执行完毕
protected void onCompleteState() {
    if (getCompleteText() != null) {
        setText(getCompleteText());
    }
    setBackgroundCompat(getCompleteDrawable());
}

// 显示为:默认状态
protected void onNormalState() {
    if (getNormalText() != null) {
        setText(getNormalText());
    }
    setBackgroundCompat(getNormalDrawable());
}

基础类FlatButton和ProcessButton已介绍完毕,有了这两个类,按钮已经具备显示各种样式,处理进度的功能了。唯一欠缺的,就是进度条以何种样式来显示。下面进入三个具体的实现类,每个类会根据自身的特点来重写drawProgress()方法。

(4).类的详解之具体实现类ActionProcessButton
本文开篇的效果图就是ActionProcessButton类实现的。来看看drawProgress()方法是如何实现的。

// 将进度条绘制到按钮底部
@Override
public void drawProgress(Canvas canvas) {
    // 当前进度条的比例
    float scale = (float) getProgress() / (float) getMaxProgress();
    // indicatorWidth:绘制区域的右侧,随着进度的进行,该值不断扩大直到按钮的宽度
    float indicatorWidth = (float) getMeasuredWidth() * scale;

    // bottom:绘制区域的顶部
    double indicatorHeightPercent = 0.05;
    int bottom = (int) (getMeasuredHeight() - getMeasuredHeight() * indicatorHeightPercent);
    // setBounds()指定绘制的区域
    getProgressDrawable().setBounds(0, bottom, (int) indicatorWidth, getMeasuredHeight());
    // 最后要调用draw()方法绘制
    getProgressDrawable().draw(canvas);
}
这里将进度条绘制到按钮底部,绘制区域中的left,top,bottom都是保持不变的,只有right随着progress值的增加不断的变大,直到达到按钮的宽度,进度执行完毕。

(5).类的详解之具体实现类SubmitProcessButton
先上效果图。


该类代码很少,除了构造方法外,只有drawProgress()方法的实现。

// 进度条填充按钮高度,只有宽度在不断的增加
@Override
public void drawProgress(Canvas canvas) {
    float scale = (float) getProgress() / (float) getMaxProgress();
    float indicatorWidth = (float) getMeasuredWidth() * scale;

    getProgressDrawable().setBounds(0, 0, (int) indicatorWidth, getMeasuredHeight());
    getProgressDrawable().draw(canvas);
}
实现方式同上。依旧是调用Drawable的setBounds()方法,修改绘制区域。保持高度不变,不断的增加宽度。

(6).类的详解之具体实现类GenerateProcessButton
先上效果图。


该类的实现几乎与SubmitProcessButton一致,唯一的区别是,在进度显示上反过来了,保持宽度不变,不断的刷新高度。

// 进度条填充按钮宽度,只有高度在不断的增加
@Override
public void drawProgress(Canvas canvas) {
    float scale = (float) getProgress() / (float) getMaxProgress();
    float indicatorHeight = (float) getMeasuredHeight() * scale;

    getProgressDrawable().setBounds(0, 0, getMeasuredWidth(), (int) indicatorHeight);
    getProgressDrawable().draw(canvas);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值