前言
最近一直在研究自定义 view, 正好项目中有一个根据下载进度来实现球体进度的需求, 所以自己写了个进度球, 代码非常简单. 先看下效果:
效果还是非常不错的.
准备知识
要实现上面的效果我们只要掌握两个知识点就好了, 一个是 Handler 机制, 用于发消息刷新我们的进度球, 一个是 clipDrawable. 网上关于 Handler 的教程很多, 这里重点介绍一下 clipDrawable, 进度球的实现全靠 clipDrawable. clipDrawable 如下图所示: ClipDrawable 和 InsertDrawable 一样继承 DrawableWrapper,DrawableWrapper 继承 Drawable.
ClipDrawable 是可以进行裁剪操作的 drawable, 提供了函数
setLevel(@IntRange(from=0,to=10000) int level)
来设置裁剪的大小. level 越大图片越大. level=0 时图片完全不显示, level=10000 时图片完全显示.
- ClipDrawable clipDrawable = (ClipDrawable) getContext().getResources().getDrawable(R.drawable.bottom_top_clip_gradient_color);// 获取图片
- clipDrawable.setLevel(100);// 进行裁剪
一般还要设置裁剪的方向, 垂直裁剪还是水平裁剪, 我们这个进度球用的是垂直从下向上裁剪. 思路: 知道了 ClipDrawable 的用法, 进度球就好实现了. 只需要一个球形的图片, 从下往上裁剪, 通过设置 setLevel 从 0 到 10000, 就可以实现进度球从 0 到进度 100 的效果了.
实现
1, 定义 BallProgress,BallProgress 继承 View, 重写 onDraw() 方法, 用于实现进度球的 view.
- /**
- * Created time 15:02.
- *
- * @author huhanjun
- * @since 2018/12/26
- */
- public class BallProgress extends View {
- private float mProgress = 0.0f; // 取值位 0 - 1.0
- private boolean selected = true;
- public BallProgress(Context context) {
- super(context);
- }
- public BallProgress(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
- public BallProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
- private void init() {
- mProgressPaint = new Paint();// 初始化, 定义画笔.
- mProgressPaint.setAntiAlias(true);// 设置抗锯齿
- }
- public float getProgress() {
- return mProgress;
- }
- public void setProgress(float progress) {// 设置进度, 通过进度的大小实现裁剪的大小
- mProgress = progress;
- invalidate();
- }
- private Paint mProgressPaint;
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- Bitmap dst = getRectangleBitmap();// 获取 bitmap
- setLayerType(LAYER_TYPE_HARDWARE, null); // 开启硬件离屏缓存
- canvas.drawBitmap(dst, 0, 0, mProgressPaint);
- }
- private Bitmap getRectangleBitmap() {
- int width = getWidth();
- int height = getHeight();
- Bitmap dstBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- ClipDrawable clipDrawable = null;
- clipDrawable = (ClipDrawable) getContext().getResources().getDrawable(R.drawable.bottom_top_clip_single_color);// 获取球形的背景图片, 用于裁剪, 就是上面看到的进度球中的图片
- clipDrawable.setBounds(new Rect(0, 0, width, height));// 设置边界
- clipDrawable.setLevel((int) (10000 * mProgress));// 设置进度,
- Canvas canvas = new Canvas(dstBitmap);// 设置画布
- clipDrawable.draw(canvas);// 绘制
- return dstBitmap;// 将 bitmap 返回
- }
- }
有了自定义的 BallProgress, 就可以在布局中使用了, 定义的 xml 文件如下:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:Android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- Android:layout_width="match_parent"
- Android:layout_height="match_parent"
- tools:context=".MainActivity">
- <com.example.admin.floatprogressbar.BallProgress
- Android:id="@+id/progress"
- Android:layout_width="@dimen/progress_size"
- Android:layout_height="@dimen/progress_size"
- Android:layout_centerInParent="true" />
- <ImageView
- Android:layout_width="@dimen/progress_size"
- Android:layout_height="@dimen/progress_size"
- Android:layout_centerInParent="true"
- Android:background="@drawable/main_tab_un_select_bg" />
- </RelativeLayout>
上面布局中的 ImageView 是悬浮球的边界. 在 MainActivity 中来定时的改变进度球的大小. 代码如下:
- public class MainActivity extends AppCompatActivity {
- private final int PROGRESS_MESSAGE = 0;
- private float progress = 0.0f;
- private BallProgress mBallProgress;
- private Handler mHandler = new Handler(new Handler.Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case PROGRESS_MESSAGE:
- progress = (progress> 0.9f) ? 0.9f : progress;
- mBallProgress.setProgress(progress);// 接收消息, 改变进度球的进度
- break;
- }
- return true;
- }
- });
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- initView();
- initAction();
- }
- private void initView() {
- mBallProgress = findViewById(R.id.progress);
- }
- // 发消息
- private void initAction() {
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- progress += 0.02f;
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- mHandler.sendEmptyMessage(PROGRESS_MESSAGE);// 每隔 100 毫秒发送一次消息, 对进度球进度进行更新.
- }
- }
- }).start();
- }
- }
上面代码在 inAction() 中开一个线程, 每隔 100 毫秒发送消息, 在 handler 中处理更新, 在 handler 使用中并没有直接重写 hanldeMessage 方法, 而是传入 Handler.Callback 并在 callback 中实现 handleMessage 方法, 这样可以防止内存泄漏. 实际应用中, 可以是跟业务相关的具体进度.
总结
自定义进度球, 用的是继承 view, 并且通过自定义画笔, 重写 onDraw() 方法来实现, 一般自定义 view 都会重写 onDraw() 方法, 一般进度条都是 ClipDrawable 来实现的. 源码地址: 进度球代码
来源: https://juejin.im/post/5c270c9051882565986a20dc