Java实现进度条

仅供学习,不可转载,欢迎评论交流。

前言

在编程开发中,经常会遇到一些需要花费较长时间的操作,比如:文件上传、数据处理、任务完成的进度等等。为了提高用户的使用体验,一般情况下我们都会利用进度条的方式来实现,让用户拥有一个比较良好的使用体验。下面将介绍四种方式使用Java实现进度条的功能。

Java进度条原理

要实现进度条,我们需要知道操作的总进度和当前进度;总进度表示操作需要完成的总任务数,当前进度指已经完成的任务数。根据这两个值,我们就可以计算出当前操作进度的百分比。

总得来说进度条的基本原理就是通过在程序汇总开辟一个新的线程,将进度条和耗时任务分离开,从而达到在运行时耗时任务的过程中展示进度条的效果。

基于Java进度条的实现方式

1、使用Swing JProgressBar工具

Swing JProgessBar是Java Swing组件库中提供的一中基于进度条实现方式,使用Java Swing JProgressBar实现进度条的主要步骤如下:

java 复制代码
package com.cpf.jdt;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
 * @author cpf
 * @DATE 2023/10/11 9:16
 */
public class JProgress {
    JFrame jf = new JFrame("测试进度条");
    JCheckBox indeterminate = new JCheckBox("不确定进度");
    JCheckBox noBorder = new JCheckBox("不绘制边框");
    //创建进度条
    JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL,0,100);
    public void init(){
        //组装视图
        //处理复选框的点击行为
        indeterminate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取indetermainate复选框是否选中
                boolean selected = indeterminate.isSelected();
                //设置当前进度条-不确定进度
                bar.setIndeterminate(selected);
                bar.setStringPainted(!selected);
            }
        });
        noBorder.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取noBorder复选框是否选中
                boolean selected = noBorder.isSelected();
                bar.setBorderPainted(!selected);
            }
        });
        Box vBox = Box.createVerticalBox();
        vBox.add(indeterminate);
        vBox.add(noBorder);
        //设置进度条的属性
        bar.setStringPainted(true);
        bar.setBorderPainted(true);
        //把当前窗口的布局方式修改为FlowLayout
        jf.setLayout(new FlowLayout());
        jf.add(vBox);
        jf.add(bar);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);
        //通过循环模拟修改进度条的进度
        for (int i = 1; i < 101; i++){
            //修改已经完成的工作量,也就是百分比
            bar.setValue(i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args){
        new JProgress().init();
    }
}

以上代码实现的效果如下图所示:

分为一下几种情况:

该显示效果是利用百分比的形式显示。

当点击不确定进度会有一个方框来回摆动,效果如下图所示:

点击不绘制边框时,显示效果如下图所示:

2、通过将耗时任务分离出来,利用子线程的方式处理

第一种方式有一个问题,就是我们展示进度时,通过一个for循环进行的,通过for循环我们模拟了一个耗时操作,很容易看出来该for循环是在主线程中进行的,也就是说耗时的操作是在主线程中进行的,如下图所示。

这样我们通过一个子线程来完成该耗时任务。

进度条在主线程中,但是耗时任务在子线程中,我们通过一个变量来记录耗时任务的进度,然后再主线程中开启一个定时器,例如200ms读取该变量值,将该变量的值显示在进度条上就可以了。

java 复制代码
package com.cpf.jdt;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author cpf
 * @DATE 2023/10/11 9:16
 */
public class JProgress1 {
    JFrame jf = new JFrame("部署进度");
    JCheckBox indeterminate = new JCheckBox("不确定进度");
    JCheckBox noBorder = new JCheckBox("不绘制边框");
    //创建进度条
    JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL,0,100);
    public void init(){
        //组装视图
        //处理复选框的点击行为
        indeterminate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取indetermainate复选框是否选中
                boolean selected = indeterminate.isSelected();
                //设置当前进度条-不确定进度
                bar.setIndeterminate(selected);
                bar.setStringPainted(!selected);
                
            }
        });
        noBorder.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取noBorder复选框是否选中
                boolean selected = noBorder.isSelected();
                bar.setBorderPainted(!selected);
            }
        });
        Box vBox = Box.createVerticalBox();
        vBox.add(indeterminate);
        vBox.add(noBorder);
        //设置进度条的属性
        bar.setStringPainted(true);
        bar.setBorderPainted(true);
        //把当前窗口的布局方式修改为FlowLayout
        jf.setLayout(new FlowLayout());
        jf.add(vBox);
        jf.add(bar);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);
        //开启子线程模拟耗时操作
        SimlaterActivity simlaterActivity = new SimlaterActivity(bar.getMaximum());
        //开启子线程
        new Thread(simlaterActivity).start();
        //设置定时任务
        Timer timer = new Timer(200, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //读取线程任务对象的当前完成量,设置给进度条
                int current = simlaterActivity.getCurrent();
                bar.setValue(current);
            }
        });
        //开启定时任务
        timer.start();
        //监听进度条的任务变化
        bar.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                int value = bar.getValue();
                if (value == simlaterActivity.getAmount()){
                    timer.stop();
                }
            }
        });
    }
    
    private class SimlaterActivity implements Runnable{
        //记录任务总量
        private int amount;
        public SimlaterActivity(int amount) {
            this.amount = amount;
        }
        //记录当前任务的完成量
        //由于子线程和主线程不是同一个线程,所以需要考虑变量的内存可见问题
        //volatile:内存可见,在该线程修改的值,其他线程可以立刻看到该线程修改的效果
        private volatile int current;
        public int getAmount() {
            return amount;
        }
        public void setAmount(int amount) {
            this.amount = amount;
        }
        public int getCurrent() {
            return current;
        }
        public void setCurrent(int current) {
            this.current = current;
        }
        @Override
        public void run() {
            //子线程的任务,模拟耗时操作
            while (current < amount){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                current++;
            }
        }
    }
    public static void main(String[] args){
        new JProgress1().init();
    }
}

显示效果与第一种方式是一致的

3、使用BoundedRangeModel的方式完成进度条功能

我们了解过Swing中很多组件的界面与数据都采用MVC的设计思想,使用MVC的设计思想可以将代码进行分块、分层:

上面介绍的JProgressBar也是采用的MVC的设计思想,其实JProgressBar组件内部也是通过BoundedRangeModel来完成数据的操作。

下面的代码是对之前代码的改进,通过BoundedRangeModel完成数据的设置、获取和监听。

java 复制代码
package com.cpf.jdt;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author cpf
 * @DATE 2023/10/11 9:16
 */
public class JProgress2 {
    JFrame jf = new JFrame("部署进度");
    JCheckBox indeterminate = new JCheckBox("不确定进度");
    JCheckBox noBorder = new JCheckBox("不绘制边框");
    //创建进度条
    JProgressBar bar = new JProgressBar(JProgressBar.HORIZONTAL,0,100);
    //获取进度条内置的数据模型对象
    BoundedRangeModel model = bar.getModel();
    public void init(){
        //组装视图
        //处理复选框的点击行为
        indeterminate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取indetermainate复选框是否选中
                boolean selected = indeterminate.isSelected();
                //设置当前进度条-不确定进度
                bar.setIndeterminate(selected);
                bar.setStringPainted(!selected);
            }
        });
        
        noBorder.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //获取noBorder复选框是否选中
                boolean selected = noBorder.isSelected();
                bar.setBorderPainted(!selected);
            }
        });
        Box vBox = Box.createVerticalBox();
        vBox.add(indeterminate);
        vBox.add(noBorder);
        //设置进度条的属性
        bar.setStringPainted(true);
        bar.setBorderPainted(true);
        //把当前窗口的布局方式修改为FlowLayout
        jf.setLayout(new FlowLayout());
        jf.add(vBox);
        jf.add(bar);
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.pack();
        jf.setVisible(true);
        //开启子线程模拟耗时操作
        SimlaterActivity simlaterActivity = new SimlaterActivity(bar.getMaximum());
        //开启子线程
        new Thread(simlaterActivity).start();
        //设置定时任务
        Timer timer = new Timer(200, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //读取线程任务对象的当前完成量,设置给进度条
                int current = simlaterActivity.getCurrent();
//                bar.setValue(current);
                model.setValue(current);
            }
        });
        //开启定时任务
        timer.start();
        //监听进度条的任务变化
//        bar.addChangeListener(new ChangeListener() {
        model.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
//                int value = bar.getValue();
                int value = model.getValue();
                if (value == simlaterActivity.getAmount()){
                    timer.stop();
                }
            }
        });
        
    }
    
    private class SimlaterActivity implements Runnable{
        //记录任务总量
        private int amount;
        public SimlaterActivity(int amount) {
            this.amount = amount;
        }
        //记录当前任务的完成量
        //volatile:内存可见,再该线程修改的值,其他线程可以立刻看到该线程修改的效果
        private volatile int current;
        public int getAmount() {
            return amount;
        }
        public void setAmount(int amount) {
            this.amount = amount;
        }
        public int getCurrent() {
            return current;
        }
        public void setCurrent(int current) {
            this.current = current;
        }
        @Override
        public void run() {
            //子线程的任务,模拟耗时操作
            while (current < amount){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                current++;
            }
        }
    }
    
    public static void main(String[] args){
        new JProgress2().init();
    }
}

显示效果与前两种方式相同

4、创建进度对话框

ProgressMonitor的用法与JProgreeBar的用法基本相似,只是ProgressMonitor可以直接创建一个进度对话框。

其中,使用ProgressMonitor创建的对话框里包含的进度条非常的固定,程序甚至不能设置改进度条是否包含边框(总是包含边框),不能设置进度不确定,不能改变进度条的方向(总是水平的)。

java 复制代码
package com.cpf.jdt;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author cpf
 * @DATE 2023/10/11 10:14
 */
public class ProgressMonitorDemo {
    Timer timer;
    public void init(){
        //创建ProgressMonitor进度对话框对象
        ProgressMonitor monitor = new ProgressMonitor(null, "等待部署完成......", "已完成:", 0, 100);
        SimlaterActivity simlaterActivity = new SimlaterActivity(100);
        new Thread(simlaterActivity).start();
        //设置定时任务
        timer = new Timer(200, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //读取当前任务量,修改进度
                int current = simlaterActivity.getCurrent();
                monitor.setProgress(current);
                //判断用户是否点击了取消按钮,点击了停止任务,关闭对话框,退出程序
                if (monitor.isCanceled()){
                    timer.stop();
                    monitor.close();
                    System.exit(0);
                }
            }
        });
        timer.start();
    }
    
    public static void main(String[] args){
        new ProgressMonitorDemo().init();
    }
    //定义一个线程任务,模拟耗时操作
    private class SimlaterActivity implements Runnable{
        //记录任务总量
        private int amount;
        public SimlaterActivity(int amount) {
            this.amount = amount;
        }
        //记录当前任务的完成量
        //volatile:内存可见,再该线程修改的值,其他线程可以立刻看到该线程修改的效果
        private volatile int current;
        public int getAmount() {
            return amount;
        }
        public void setAmount(int amount) {
            this.amount = amount;
        }
        public int getCurrent() {
            return current;
        }
        public void setCurrent(int current) {
            this.current = current;
        }
        @Override
        public void run() {
            //子线程的任务,模拟耗时操作
            while (current < amount){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                current++;
            }
        }
    }
    
}

显示效果如下图所示:

5、总结

Java实现进度条功能作为一种常见的实现进度条的方式,其基本原理是通过在程序中开辟一个新的线程,将进度条和耗时任务分离开,从而达到在运行耗时任务的过程中展示进度条的效果。以上提供了四种方式实现进度条的场景,针对一些有较高交互需求的场景,可以采用前后端相互配合实现进度条及时准确更新效果的方式实现更加优秀的用户交互体验。

相关推荐
Chen-Edward1 小时前
有了Spring为什么还有要Spring Boot?
java·spring boot·spring
magic334165631 小时前
Springboot整合MinIO文件服务(windows版本)
windows·spring boot·后端·minio·文件对象存储
开心-开心急了1 小时前
Flask入门教程——李辉 第一、二章关键知识梳理(更新一次)
后端·python·flask
掘金码甲哥2 小时前
调试grpc的哼哈二将,你值得拥有
后端
陈小桔2 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!2 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36782 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July2 小时前
Hikari连接池
java
微风粼粼2 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad3 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud