android 使用Drawable实现加载动画效果的按钮

前言

最近项目有个需求, 需要一个展示下载状态的按钮, 类似这样:
android 使用Drawable实现加载动画效果的按钮_第1张图片

这个效果有多种方法都可以实现, 最初的思路是在自定义view里封装一个ProgressBar/Button, 然后根据状态设置两个控件Visibility, 后来想了想还是放弃了ProgressBar, 直接使用了ClipDrawable 作为 BackgroundDrawable 实现的, 感觉更方便一些.

Github 上存有完整代码: https://github.com/YouCii/LearnApp/blob/master/app/src/main/java/com/youcii/mvplearn/widget/DownLoadButton.kt
转载请标明出处, 原文地址:
https://blog.csdn.net/j550341130/article/details/83993676


核心类

1. ClipDrawable
这是一个按比例裁剪的Drawable, 可以使用它实现按百分比进度填充红色的部分. Clip的布局 bg_download_button_clip 具体写法如下:

<clip
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="horizontal"
    android:drawable="@drawable/bg_download_button_downloading"
    android:gravity="left"/>

其中 android:gravity 是用来设置裁剪起始位置, 有多种属性可以设置, 具体可以搜一下.
内部的 bg_download_button_downloading 就是一个完整填充的红色背景

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="100px"/>
    <solid android:color="#D8413A"/>
shape>

在代码中获取方法与普通Drawable一样

private var solidDrawable: Drawable? = resources.getDrawable(R.drawable.bg_download_button_clip)

然后通过设置solidDrawablelevel来实现进度, level最大是10000, 所以设置进度时需要这样做

solidDrawable?.level = currentPercent * 100

注意, ClipDrawable只是一个按某规则进行裁剪的Drawable, 最外部是没有边框的, 想要一个固定存在的边框的话仅用ClipDrawable就不够了, 需要叠加一个普通的边框Drawable, 这就需要用到LayerDrawable了.

2. LayerDrawable
LayerDrawable可以把多个Drawable叠加到一起, 在这里需要使用它拼合 进度填充ClipDrawable + 普通边框效果Drawable, 实现最终效果.
普通边框效果的布局如下:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="100px"/>
    <solid android:color="#FFFFFF"/>
    <stroke
        android:width="2px"
        android:color="#D8413A"/>
shape>

拼接需要在代码中实现:

private var layerDrawable: Drawable? = LayerDrawable(arrayOf(background, solidDrawable))

其中background就是边框Drawable, 实例化LayerDrawable时传入需要叠加的Drawable数组即可


完整 DownLoadButton 代码

内部使用的Drawable布局可以看下Github的Demo:
https://github.com/YouCii/LearnApp/blob/master/app/src/main/java/com/youcii/mvplearn/widget/DownLoadButton.kt

package com.youcii.mvplearn.widget

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.widget.Button
import com.youcii.mvplearn.R

/**
 * Created by jdw on 2018/11/2.
 *
 * 状态转换按钮, 带有边框填充效果的下载进度
 */
class DownLoadButton @JvmOverloads constructor(context:Context,attrs:AttributeSet?=null,defStyleAttr:Int=0) 
: Button(context, attrs, defStyleAttr) {

    /**
     * 当前状态
     */
    private var currentState = 0
    /**
     * 当前下载进度
     * 百分比
     */
    private var currentPercent = 0

    private var onDownLoadButtonClickListener: OnDownLoadButtonClickListener? = null

    init {
        gravity = Gravity.CENTER
        currentState = STATE_NO_DOWNLOAD

        setOnClickListener { v -> onDownLoadButtonClickListener?.onClick(v, currentState) }
    }

    /**
     * 设置当前状态
     */
    fun setState(state: Int) {
        this.currentState = state
        postInvalidate()
    }

    /**
     * 设置下载进度
     */
    fun setDownLoadProgress(percent: Int) {
        this.currentPercent = percent
        postInvalidate()
    }

    private var solidDrawable: Drawable? = null

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        when (currentState) {
            STATE_NO_DOWNLOAD -> {
                currentPercent = 0

                text = "下载"
                setTextColor(Color.parseColor("#D8413A"))
                setBackgroundResource(R.drawable.bg_download_button_download)
            }
            STATE_DOWNLOADING -> {
                if (text != "下载中") {
                    text = "下载中"
                    setTextColor(Color.parseColor("#000000"))

                    // 动态填充的Drawable
                    solidDrawable = resources.getDrawable(R.drawable.bg_download_button_clip)
                    // 原background是显示的边框, 合并后显示
                    background = LayerDrawable(arrayOf(background, solidDrawable))
                }
                // 利用ClipDrawable的裁剪效果实现进度展示
                solidDrawable?.level = currentPercent * 100
            }
            STATE_COMPLETE -> {
                text = "立即使用"
                setTextColor(Color.parseColor("#FFFFFF"))
                setBackgroundResource(R.drawable.bg_download_button_to_use)
            }
            STATE_USED -> {
                text = "使用中"
                setTextColor(Color.parseColor("#000000"))
                setBackgroundResource(R.drawable.bg_download_button_using)
            }
        }
    }


    fun setOnDownLoadButtonClickListener(onDownLoadButtonClickListener: OnDownLoadButtonClickListener) {
        this.onDownLoadButtonClickListener = onDownLoadButtonClickListener
    }

    interface OnDownLoadButtonClickListener {
        fun onClick(view: View, currentState: Int)
    }

    companion object {
        // 未下载
        val STATE_NO_DOWNLOAD = 0
        // 下载中
        val STATE_DOWNLOADING = 1
        // 下载完成
        val STATE_COMPLETE = 2
        // 已使用
        val STATE_USED = 3
    }
}

其他尝试

下载进度动画效果的实现 除了 ClipDrawable / Progress + Button 之外, 当时还尝试了一种Drawable.setBounds(int left, int top, int right, int bottom), 这四个参数指的是drawablecanvas绘制时要放在多大的矩形区域内, 也就是说Drawable是被完整绘制出来的, 只不过是被压缩或者拉伸了. 对于我们需要的这种圆角背景, 进度非常小的时候就会出现圆角消失的情况, 所以不采用. 具体代码如下:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // ...
            case STATE_DOWNLOADING:
                //计算当前进度所需宽度
                int downLoadedWidth = (int) (getMeasuredWidth() * ((double) curPrecent / 100));
                Rect rect = new Rect(0, 0, downLoadedWidth, getMeasuredHeight());
                downLoadBackground.setBounds(rect); // 带边框, 满红色填充, 类似于下载完成样式的Drawable
                downLoadBackground.draw(canvas);
                break;
        // ...
    }

你可能感兴趣的:(Android知识)