【Swing】 图片幻灯式浏览软件PicturesShow

现世图片浏览软件基本都有幻灯秀的模式,但普遍存在两个弊端:一是仅限于单目录自动展示,多目录浏览仍需手动,长此以往便费时费力;二是图片展现方式如百叶窗淡入淡出等多而无用,有抓不住用户真实需求的嫌疑。作为一个资深的图片收藏者,我总结出来浏览图片最好的方式便是由上至下平滑滑动的网页浏览模式,就像自己轻轻拨动鼠标滚轮使网页自然滚动一样,而最好的运行方式是一经启动就不用操心,软件自己就会进行深度广度遍历,而做到这两点的市面上所谓看图软件一个都不存在,而我又是一个程序员,可以自己动手丰衣足食,于是便开始了新作PicturesShow。最终只用了两个类实现了自己的想法,总用时不到三天,打出的jar包不到15k。是一款纯绿色软件。

在研发过程中我发现单目录图片过多便容易产生目录转换卡顿、图片上移跳窜的现象,于是我革新了构建缓冲区图片的数据结构,让其一次性显示12张图片,多余的图片在后台等待,这样一举便解决了卡顿跳窜的问题,同时还保留了之前的分目录依次浏览图片的合理方式。

1.021版下载地址: https://files.cnblogs.com/files/heyang78/picturesShow1.021_210807.rar ,其中含有可执行jar,批处理文件和配置文件。

1.022版下载地址:https://files.cnblogs.com/files/heyang78/picturesShow1022final_src_210808.rar,其中含有执行文件(可执行jar,批处理文件和配置文件),源码和屏幕截图。

软件功能:以幻灯方式展示您平时收藏的放在多个目录中的jpg/png图片,按键盘就可切换目录,双击鼠标(左右键皆可)便能暂停图片上移,再次双击便又重启。

软件优势:平滑流畅展示图片,启动后不用管,图多不卡顿。

使用前提:安装JDK1.8,若不会请参考 https://www.cnblogs.com/heyang78/p/7429783.html

使用说明:解开压缩包,点击run.bat执行,或是进入压缩包所在目录,调出控制台,输入命令 java -jar picturesShow.jar执行。

 

软件截图:

设定界面:

运行界面:

代码:

Inlet类:

复制代码
package com.hy;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;

// 入口类
public class Inlet {
    // 用来做设定的界面
    static JFrame settingFrame;
    // 用来做展示的界面
    static ShowGui showGui;
    // 图片所在目录路径
    static JTextField folderTxt;
    // 图片显示间隔
    static JTextField intervalTxt;
    // 图片上移速度
    static JTextField upSpeedTxt;
    // 界面参数保存文件名
    static final String Setting_File_Name="setting.txt";
    
    // 从文件获得界面初始值
    private static void loadDefaultValues() {
        List<String> lines=new ArrayList<String>();
        
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(Setting_File_Name), "GBK"));
            String line = null;
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }
            
            System.out.println("读取数据行:"+lines.size()+"行.");
            if(lines.size()>=3) {
                folderTxt.setText(lines.get(0));
                intervalTxt.setText(lines.get(1));
                upSpeedTxt.setText(lines.get(2));
            }
            
            br.close();
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    
    // 将界面初始值存入文件
    private static void saveDefaultValues() {
        // 从界面取值
        String folder=folderTxt.getText();
        String interval=intervalTxt.getText();
        String upSpeed=upSpeedTxt.getText();
        
        PrintWriter out;
        try {
            out = new PrintWriter(Setting_File_Name); // 这里是写输出文件的位置

            out.println(folder);
            out.println(interval);
            out.println(upSpeed);

            out.close();
            System.out.println("存储数据完毕.");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        // 取得屏幕宽度
        double screenWidth = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        
        // 取得屏幕高度
        double screenHeight = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        
        //-- 创建展示界面
        showGui=new ShowGui();
        showGui.setTitle("PicturesShow 图片幻灯展示1.02 提示:按任意键切换目录,双击鼠标左右键暂停/启动  作者:何杨 horn19782016@163.com");
        showGui.setSize((int)screenWidth, (int)screenHeight);
        showGui.setVisible(false);
        showGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        //-- 创建设定界面
        settingFrame=new JFrame("PicturesShow 图片幻灯展示1.02 作者:何杨 horn19782016@163.com");
        // 点击关闭按钮退出
        settingFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置宽高
        settingFrame.setSize(420, 280);
        
        // 设置展示居中显示
        settingFrame.setLocation( (int) (screenWidth - settingFrame.getWidth()) / 2,
        (int) (screenHeight - settingFrame.getHeight()) / 2);
        
        //-- 添加组件
        JPanel panel=new JPanel();// 新建容器面板
        panel.setBorder(new EmptyBorder(5,5,5,5));// 设置边界
        panel.setLayout(new GridBagLayout());// 设置布局
        
        GridBagConstraints c=new GridBagConstraints();        
        c.fill=GridBagConstraints.HORIZONTAL;// 填充模式为水平充满
        
        c.weightx=0.0;// 权重0,表示不占用多余空间
        c.gridx=0;
        c.gridy=0;// 占左上
        panel.add(new JLabel("图片所在目录:"),c);
        
        c.weightx=1.0;// 权重1,多余空间全归属到此
        c.gridx=1;
        c.gridy=0;// 占右上
        c.insets=new Insets(0,5,0,0);// 左边填充五个像素
        folderTxt=new JTextField("C:\\hy\\pic",15);
        panel.add(folderTxt,c);
        
        c.weightx=0.0;
        c.gridx=0;
        c.gridy=1;// 占中左
        c.insets=new Insets(10,0,0,0);// 上边填充五个像素
        panel.add(new JLabel("图片展示间隔:"),c);
        
        c.weightx=1.0;
        c.gridx=1;
        c.gridy=1;// 占中右
        c.insets=new Insets(0,5,0,0);
        intervalTxt=new JTextField("30",15);
        panel.add(intervalTxt,c);
        
        c.weightx=0.0;
        c.gridx=0;
        c.gridy=2;// 占下左
        c.insets=new Insets(5,0,0,0);// 上边填充五个像素
        panel.add(new JLabel("图片上移速度:"),c);
        
        c.weightx=1.0;
        c.gridx=1;
        c.gridy=2;// 占下右
        c.insets=new Insets(0,5,0,0);
        upSpeedTxt=new JTextField("2",15);
        panel.add(upSpeedTxt,c);
        
        c.weightx=0.0;
        c.gridx=0;
        c.gridy=3;// 占底部
        c.insets=new Insets(5,0,0,0);
        c.gridwidth=2;// 跨两格
        c.fill=GridBagConstraints.CENTER;// 居中而不填充,取默认大小
        JButton showBtn=new JButton("开始展示图片");
        panel.add(showBtn,c);
        
        c.weightx=1.0;
        c.weighty=1.0;
        c.gridx=0;
        c.gridy=4;// 占底部
        c.insets=new Insets(5,0,0,0);
        c.gridwidth=2;// 跨两格
        c.fill=GridBagConstraints.BOTH;// 居中而不填充,取默认大小
        JTextArea area=new JTextArea(5,10);
        area.setLineWrap(true);
        area.setText("说明:本软件会深度遍历您指定的目录并展示每个目录中的jpg和png图片,目录中图片数量不限,图片展示间隔建议取1~10,图片上移速度建议取1~10. 本软件为个人或会议时欣赏图片使用,如果您觉得不错且有工作机会提供请联系:horn19782016@163.com");
        JScrollPane jsp=new JScrollPane(area,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        panel.add(jsp,c);
        
        loadDefaultValues();// 设置界面初值
        
        showBtn.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent e) {
                 String folderPath=folderTxt.getText();
                 File folder=new File(folderPath);
                 
                 // 目录检查
                 if(folder.exists() && folder.isDirectory()) {
                     showGui.setRoot(folderPath);
                     //showGui.setFolder(folderPath);
                 }else {
                     JOptionPane.showMessageDialog(null, "‘图片所在目录:"+folderPath+"’不存在或不是目录.", "目录设定错误",JOptionPane.PLAIN_MESSAGE);
                     folderTxt.requestFocus();
                     return;
                 }    
                 
                 // 显示间隔检查
                 String strInterval=intervalTxt.getText();
                 if(isDigital(strInterval)) {
                     showGui.setInterval(Integer.parseInt(strInterval));
                 }else {
                     JOptionPane.showMessageDialog(null, "‘图片显示间隔:"+strInterval+"’不是数字.", "间隔设定错误",JOptionPane.PLAIN_MESSAGE);
                     intervalTxt.requestFocus();
                     return;
                 }
                 
                 // 上移速度检查
                 String strUpSpeed=upSpeedTxt.getText();
                 if(isDigital(strUpSpeed)) {
                     showGui.setUpSpeed(Integer.parseInt(strUpSpeed));
                 }else {
                     JOptionPane.showMessageDialog(null, "‘图片上移速度:"+strUpSpeed+"’不是数字.", "速度设定错误",JOptionPane.PLAIN_MESSAGE);
                     upSpeedTxt.requestFocus();
                     return;
                 }
                 
                 saveDefaultValues();
                 settingFrame.setVisible(false);// 设定界面隐藏
                 showGui.setVisible(true);// 显示界面显示
                 showGui.startTimer();
             }
        });
        
        settingFrame.add(panel);
        
        // 显示frame
        settingFrame.setVisible(true);        
    }
    
    // 判断文本是否整数
    private static boolean isDigital(String str) {
        final String patternStr = "\\d+";
        return Pattern.matches(patternStr, str);
    }
}
复制代码

 

ShowGui类:

复制代码
package com.hy;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.Timer;

/**
 * 显示图片的界面
 * 
 * @author heyang
 *
 */
public class ShowGui extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;

    // 图片显示间隔
    private int interval;

    // 图片上卷定时器
    private Timer timer;

    // 图片上移距离
    private int upMoved = 0;

    // 图片上移速度
    private int upSpeed = 1;

    // 图像缓冲区
    private Image bufImg;

    // 图片序列,一个目录下的所有图片文件名列表
    private List<String> allPictures;

    // 所有包含有图片文件的目录列表 
    private List<String> folderSeries;
    
    // 当然目录所在索引
    private int folderIndex = 0;

    // 构造函数
    public ShowGui() {
        // 添加键盘处理
        this.addKeyListener(new KeyAdapter() {
            public void keyTyped(KeyEvent e) {
                // 按键盘切换到随机目录
                jumpToRandomFolder();
            }
        });
        
        // 添加鼠标处理
        this.addMouseListener(new MouseListener() {
            @Override
            public void mousePressed(MouseEvent e) {
                // do nothing
            }
            @Override
            public void mouseEntered(MouseEvent e) {
                // do nothing
            }
            @Override
            public void mouseClicked(MouseEvent e) {
                if(e.getClickCount()==2) {
                    pauseRestartTimer();
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                // do nothing
            }

            @Override
            public void mouseExited(MouseEvent e) {
                // do nothing
            }
        });
    }
    
    // 如果timer在运作就暂停,如果其暂停就重启
    private void pauseRestartTimer() {
        if(timer.isRunning()) {
            timer.stop();
        }else {
            timer.restart();
        }
    }
    
    // 转到随机目录
    private void jumpToRandomFolder() {
        folderIndex=rnd(0,folderSeries.size() - 1);
        changeFolder(folderSeries.get(folderIndex));
        upMoved = 0;
    }
    
    // 取得min和max之间包括端点的随机整数
    private static int rnd(int min,int max) {
        return (int)(min+Math.random()*(max-min+1));
    }
    
    // 转到下一目录
    private void jumpToNextFolder() {
        folderIndex++;
        if (folderIndex > folderSeries.size() - 1) {
            folderIndex = 0;
        }
        changeFolder(folderSeries.get(folderIndex));
        upMoved = 0;
    }

    // 绘制缓冲区图片
    /**
     * 
     */
    private void buildBufImg() {
        final int topPaddding = 100;
        final int TextHeight = 50;
        final int Picture_Num_InShowing=12;
        
        //-- 准备十二使徒
        int startIdx=0;
        int endIdx=allPictures.size()>Picture_Num_InShowing?Picture_Num_InShowing:allPictures.size();
        
        // 填充十二使徒
        List<String> apostles=new ArrayList<String>();
        for(int i=startIdx;i<endIdx;i++) {
            apostles.add(allPictures.get(i));
        }
        // 删除由十二使徒展示的部分
        allPictures.removeAll(apostles);

        // -- 这里往下是创建BufImg
        int bufImgHeight = topPaddding;
        for (int i = 0; i < apostles.size(); i++) {
            String iconPath = apostles.get(i);
            ImageIcon icon = new ImageIcon(iconPath);

            bufImgHeight += icon.getIconHeight();
            bufImgHeight += TextHeight;// 这个高度是留出来写字的
        }
        bufImg = createImage(this.getWidth(), bufImgHeight);// 创建一个垒起来的缓冲区图像,注意createImage的调用必须在paint方法之后

        // 填背景色
        Graphics g = bufImg.getGraphics();// 获取图像上下文对象
        g.setColor(Color.black);// 画黑色背景
        g.fillRect(0, 0, bufImg.getWidth(null), bufImg.getHeight(null));

        int pictureLeftY = topPaddding;// 图片左上角纵坐标
        final int Text_Vertical_offset = 15;

        // 写开场语
        g.setColor(Color.WHITE);
        g.setFont(new Font("宋体", Font.BOLD, 16));
        String startWords = "Start";
        g.drawString(startWords, this.getWidth() / 2, pictureLeftY - TextHeight / 2);

        // 往缓冲区叠图
        for (int i = 0; i < apostles.size(); i++) {
            String iconPath = apostles.get(i);
            ImageIcon icon = new ImageIcon(iconPath);
            int iconWidth = icon.getIconWidth();
            int iconHeight = icon.getIconHeight();

            // 图片左上角横坐标
            int picutureLeftX = (int) (this.getWidth() - iconWidth) / 2;

            // 叠图
            g.drawImage(icon.getImage(), picutureLeftX, pictureLeftY, iconWidth, iconHeight, null);

            // 写图片文字
            g.setColor(Color.WHITE);
            g.setFont(new Font("宋体", Font.PLAIN, 16));
            String pictureLabel = (i + 1) + "/" + apostles.size() + "/"+allPictures.size()+" ";
            pictureLabel += iconPath;
            g.drawString(pictureLabel, picutureLeftX, pictureLeftY - TextHeight / 2 + Text_Vertical_offset);// 15是文本竖向偏移值

            pictureLeftY += iconHeight + TextHeight;
        }

        // 写结束语
        g.setColor(Color.WHITE);
        g.setFont(new Font("宋体", Font.BOLD, 16));
        String endWords = "End";
        g.drawString(endWords, this.getWidth() / 2, pictureLeftY - TextHeight / 2 + Text_Vertical_offset);// 15是文本竖向偏移值
    }

    // 绘制图片的核心函数
    public void paint(Graphics g) {
        if (folderIndex > folderSeries.size() - 1) {
            // 到这里表示所有目录已经循环完了
            System.out.println("所有目录已经循环一遍了.");
            upMoved = 0;
            Collections.shuffle(folderSeries);// 打乱顺序
            folderIndex = 0;// 暂定重头再来过
            changeFolder(folderSeries.get(folderIndex));
            return;
        }

        if (bufImg == null) {
            // 空才需要创建,如果不加判断总调用画面会出现晃动
            buildBufImg();
        }

        int bufferredImageHieght = bufImg.getHeight(null);

        if (upMoved > bufferredImageHieght + 10) {
            // 进入这里表示浏览到了底部
            upMoved=0;
            
            if(allPictures.size()==0) {
                // 所有图片都被浏览完毕了
                jumpToNextFolder();
            }else {
                // 还有剩余图片,需要重建BufImg
                bufImg = null;
                buildBufImg();
            }
        } else {
            g.drawImage(bufImg, 0, -upMoved, this);
        }
    }

    // 启动定时器
    public void startTimer() {
        timer = new Timer(this.interval, this);// Timer的刷新间隔为10,比较合适不容易闪烁
        timer.start();
    }

    // 对Timer的响应函数。使定时重绘
    public void actionPerformed(ActionEvent e) {
        upMoved += upSpeed;
        repaint();
    }

    // 设置根目录
    public void setRoot(String root) {
        folderSeries = new ArrayList<String>();
        folderIndex = 0;

        tranversal(root);

        Collections.shuffle(folderSeries);

        System.out.println("包含jpg文件的目录共有:" + folderSeries.size() + "个.它们是:");

        for (int i = 0; i < folderSeries.size(); i++) {
            System.out.println(folderSeries.get(i));
        }

        changeFolder(folderSeries.get(folderIndex));
    }

    // 递归遍历目录
    private void tranversal(String folder) {
        File localFolder = new File(folder);
        File[] files = localFolder.listFiles();

        // 看目录里是否有图片文件
        for (int i = 0; i < files.length; i++) {
            if (files[i].isFile() && (files[i].getName().endsWith(".jpg") || files[i].getName().endsWith(".png"))) {
                // 表明此目录有图片内容,此目录有效
                folderSeries.add(folder);
                break;
            }
        }

        // 遍历子目录
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                // 如果是目录则深度遍历
                tranversal(files[i].getAbsolutePath());
            }
        }
    }

    // 变更目录
    private void changeFolder(String folder) {
        allPictures = new ArrayList<String>();

        File localFolder = new File(folder);
        File[] files = localFolder.listFiles();
        for (int i = 0; i < files.length; i++) {
            if (files[i].isFile() && (files[i].getName().endsWith(".jpg") || files[i].getName().endsWith(".png"))) {
                String picFilePath = files[i].getAbsolutePath();
                allPictures.add(picFilePath);
                // System.out.println(oldFilename);
            }
        }

        // 按字母序排序
        //Collections.sort(allPictures);

        bufImg = null;
    }

    public int getInterval() {
        return interval;
    }

    public void setInterval(int interval) {
        this.interval = interval;
    }

    public int getUpSpeed() {
        return upSpeed;
    }

    public void setUpSpeed(int upmoveSpeed) {
        this.upSpeed = upmoveSpeed;
    }
}
复制代码

谨以此软件献给天下的美图爱好者们。

posted @   逆火狂飙  阅读(220)  评论(8编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
历史上的今天:
2015-08-06 【Canvas与艺术】绘制印度国旗
2014-08-06 【高中数学/排列组合】编号为1,2,3,4,5,6的六个人分别去坐编号为1,2,3,4,5,6的六个座位,其中有且仅有两个人的编号与座位号一致的坐法有几种?
2014-08-06 【Canvas与标牌】内凹圆角矩形排列组合标牌
2014-08-06 【Canvas与旗帜】古巴人民共和国国旗
生当作人杰 死亦为鬼雄 至今思项羽 不肯过江东
点击右上角即可分享
微信分享提示