天天看點

Java Swing交易市場(C/S模型)結構UI界面連接配接伺服器項目連結

Java Swing交易市場

  • 結構
  • UI界面
    • 元件
      • 文本輸入框
      • 按鈕
      • 比較常用的元件
    • 動畫和繪畫
    • 界面建構
      • 簡單界面布局
      • 複雜界面布局(GridBagLayout)
  • 連接配接伺服器
    • servlet連接配接
    • socket
  • 項目連結

結構

C/S結構進行開發,将用戶端UI等設計與後端伺服器等分離開,分别進行開發

  • C/S結構
  • MVC結構
  • Socket和Servlet并用

UI界面

元件

文本輸入框

交易市場比較常用的元件之一就是文本輸入框,用于使用者名的輸入,密碼的輸入,或者商品搜尋功能

/**
  * @Description 文字輸入框
  * @author 廣闊大世界
  * @Date 2020/3/31
  */
public class TextEdit extends JPanel {
    protected final Color errorColor = MaterialColors.COSMO_RED;
    protected final Color focusColor = MaterialColors.COSMO_BLUE;
    protected final Color normalColor = MaterialColors.COSMO_DARK_GRAY;
    protected int hintTextSize = 12;
    protected int column = 22;
    //繪制時的顔色,和計時器結合使用
    protected Color paintColor;
    //行尾圖示
    protected ImageIcon endImage;
    protected boolean focused;
    //提示文字
    protected String hintText;
    //出錯文字,不為空則改變輸入框顔色
    protected String errorText;
    //文字輸入框本體
    protected JTextField textField;
    //行尾圖示的容器,可以增加監聽
    protected JLabel endLabel;
    //動畫計數
    protected int num = 0;
    //動畫分割數量
    protected int max = 10;
    protected Timer timer;
    public TextEdit() {
        this("");
    }
    public TextEdit(String hint){
        super();
        this.hintText = hint;
        initial();
    }

    protected void initial(){
        if (this.textField == null){
            this.textField = new JTextField();
        }
        this.setLayout(new FlowLayout(FlowLayout.LEFT));
        this.add(textField);
        this.textField.setColumns(column);
        this.textField.setBackground(null);
        this.textField.setBorder(null);
        this.textField.setFont(new Font("宋體",Font.BOLD,(int)(hintTextSize * 1.5f)));
        this.setBorder(new EmptyBorder(hintTextSize + 1,0,hintTextSize,0));
        //設定監聽,在擷取焦點之後重繪顔色
        this.textField.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                TextEdit.this.focused = true;
                if (timer != null && timer.isRunning()){
                    timer.stop();
                }
                timer = new Timer(20,new GetFocusedListener());
                timer.start();
            }

            @Override
            public void focusLost(FocusEvent e) {
                TextEdit.this.focused = false;
                if (timer != null && timer.isRunning()){
                    timer.stop();
                }
                timer = new Timer(10,new LoseFocusListener());
                timer.start();
            }
        });
        //繪制行尾圖示
        endLabel = new JLabel(endImage);
        this.add(endLabel);
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2=(Graphics2D) g;
        int h = this.getHeight();
        int w = this.textField.getWidth();
        //設定繪圖品質
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        //繪制下劃線
        if (errorText != null && errorText.length() > 0){
            paintColor = errorColor;
        } else if (num == max){
            paintColor = focusColor;
        } else if (num == 0){
            paintColor = normalColor;
        }
        g2.setColor(paintColor);
        g2.setStroke(new BasicStroke(1.5f));
        g2.drawLine(0,h- hintTextSize - 1,w + 36,h - hintTextSize - 1);
        //下劃線繪制結束
        Paint textPaint;
        //繪制錯誤資訊(不為空時)
        if (errorText != null && errorText.length() > 0){
            textPaint = new GradientPaint(0,0,errorColor,0,0,errorColor);
            g2.setPaint(textPaint);
            Font font = new Font("微軟雅黑",Font.PLAIN,hintTextSize - 1);
            g2.setFont(font);
            g2.setStroke(new BasicStroke(1));
            g2.drawString(errorText,0,h - 2);
        }
        //繪制提示
        if (hintText != null && hintText.length() > 0){
            if (focused){
                textPaint = new GradientPaint(0,0,focusColor,0,0,focusColor);
            } else if (textField.getText() != null && textField.getText().length() > 0){
                textPaint = new GradientPaint(0,0,MaterialColors.GRAY_700,0,0,MaterialColors.GRAY_700);
            } else {
                textPaint = new GradientPaint(0,0,normalColor,0,0,normalColor);
            } if (errorText != null && errorText.length() > 0){
                textPaint = new GradientPaint(0,0,errorColor,0,0,errorColor);
            }
            Font font = new Font("微軟雅黑",Font.PLAIN,hintTextSize);
            g2.setPaint(textPaint);
            g2.setFont(font);
            g2.setStroke(new BasicStroke(1));
            g2.drawString(hintText,0,hintTextSize);
        }

        g2.dispose();
    }

    //設定初始文本
    public void setText(String text){
        this.textField.setText(text);
        repaint();
    }

    public String getText(){
        return this.textField.getText();
    }

    public String getHintText() {
        return hintText;
    }

    public void setHintText(String hintText) {
        this.hintText = hintText;
        repaint();
    }

    public String getErrorText() {
        return errorText;
    }

    /**
     * @Description 設定錯誤資訊,設為空或者""則不顯示
     * @param errorText 出錯時的文本
     * @return void
     * @author 廣闊大世界
     * @Date 2020/3/31
     */
    public void setErrorText(String errorText) {
        this.errorText = errorText;
        repaint();
    }

    public int getColumn() {
        return column;
    }

    public void setColumn(int column) {
        this.column = column;
        this.textField.setColumns(column);
        repaint();
    }

    public ImageIcon getEndImage() {
        return endImage;
    }

    public void setEndImage(ImageIcon endImage) {
        this.endImage = endImage;
        this.endLabel.setIcon(this.endImage);
        repaint();
    }

    public JLabel getEndLabel() {
        return endLabel;
    }
        //擷取焦點後的行為
    protected class GetFocusedListener implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            //繪制随時間變化的顔色
            double individualR = ( 1.0 * focusColor.getRed() - normalColor.getRed()) / max;
            double individualG = ( 1.0 * focusColor.getGreen() - normalColor.getGreen()) / max;
            double individualB = ( 1.0 * focusColor.getBlue() - normalColor.getBlue()) / max;
            num++;
            if (num <= max){
                paintColor = new Color(normalColor.getRed() + (int)individualR * num,
                        normalColor.getGreen() + (int)individualG * num,
                        normalColor.getBlue() + (int)individualB * num
                        );
            } else {
                timer.stop();
            }
            repaint();
        }
    }

    //失去焦點後的行為
    protected class LoseFocusListener implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            //繪制随時間變化的顔色
            double individualR = ( 1.0 * focusColor.getRed() - normalColor.getRed()) / max;
            double individualG = ( 1.0 * focusColor.getGreen() - normalColor.getGreen()) / max;
            double individualB = ( 1.0 * focusColor.getBlue() - normalColor.getBlue()) / max;
            num--;
            if (num >= 0){
                paintColor = new Color(normalColor.getRed() + (int)individualR * num,
                        normalColor.getGreen() + (int)individualG * num,
                        normalColor.getBlue() + (int)individualB * num
                );
            } else {
                timer.stop();
            }
            repaint();
        }
    }
}
           

這裡的文本輸入框實作了懸浮标題,以及錯誤提示,在擷取到焦點之後和失去焦點之後會有一段動畫效果,界面效果還可以

按鈕

按鈕也是很常用的元件,計劃是做了兩個button一個是有陰影的,另一個是扁平的,這裡隻貼出了扁平的按鈕

public class FlatButton extends JButton implements MouseListener{
    protected String text;
    protected Icon icon;
    protected Color normalColor;
    protected Color hoverColor;
    protected Color pressedColor;
    protected Color normalTextColor;
    protected Color hoverTextColor;
    protected Color pressedTextColor;
    protected boolean selected;
    private boolean hover = false;
    public FlatButton(String text, Icon icon) {
        this(text,icon, MaterialColors.GRAY_800,MaterialColors.GRAY_700,MaterialColors.GRAY_600,MaterialColors.WHITE,MaterialColors.WHITE,MaterialColors.BLUE_400);
    }

    public FlatButton(String text, Icon icon, Color normalColor, Color hoverColor, Color pressedColor, Color normalTextColor, Color hoverTextColor, Color pressedTextColor) {
        super(text,icon);
        this.text = text;
        this.icon = icon;
        this.normalColor = normalColor;
        this.hoverColor = hoverColor;
        this.pressedColor = pressedColor;
        this.normalTextColor = normalTextColor;
        this.hoverTextColor = hoverTextColor;
        this.pressedTextColor = pressedTextColor;
        init(text,icon);
    }

    @Override
    protected void init(String text, Icon icon) {
        super.init(text, icon);
        this.setBorderPainted(false);
        this.setFocusPainted(false);
        this.setBackground(normalColor);
        this.setForeground(normalTextColor);
        this.addMouseListener(this);
    }

    public Color getNormalColor() {
        return normalColor;
    }

    public void setNormalColor(Color normalColor) {
        this.normalColor = normalColor;
        this.setBackground(normalColor);
        repaint();
    }

    public Color getHoverColor() {
        return hoverColor;
    }

    public void setHoverColor(Color hoverColor) {
        this.hoverColor = hoverColor;
        repaint();
    }

    public Color getPressedColor() {
        return pressedColor;
    }

    public void setPressedColor(Color pressedColor) {
        this.pressedColor = pressedColor;
        repaint();
    }

    public Color getNormalTextColor() {
        return normalTextColor;
    }

    public void setNormalTextColor(Color normalTextColor) {
        this.normalTextColor = normalTextColor;
        repaint();
    }

    public Color getHoverTextColor() {
        return hoverTextColor;
    }

    public void setHoverTextColor(Color hoverTextColor) {
        this.hoverTextColor = hoverTextColor;
        repaint();
    }

    public Color getPressedTextColor() {
        return pressedTextColor;
    }

    public void setPressedTextColor(Color pressedTextColor) {
        this.pressedTextColor = pressedTextColor;
        repaint();
    }

    public Boolean getSelected() {
        return selected;
    }

    @Override
    public void setSelected(boolean b) {
        super.setSelected(b);
        this.selected = b;
        if (b){
            this.setBackground(pressedColor);
        } else {
            this.setBackground(normalColor);
        }
        this.setForeground(normalTextColor);
        repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {

    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.setForeground(this.pressedTextColor);
        this.setBackground(this.pressedColor);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (hover){
            this.setBackground(this.hoverColor);
            this.setForeground(this.hoverTextColor);
        } else {
            this.setForeground(this.normalTextColor);
            this.setBackground(this.normalColor);
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        hover = true;
        if (!selected){
            this.setBackground(this.hoverColor);
            this.setForeground(this.hoverTextColor);
        }
    }

    @Override
    public void mouseExited(MouseEvent e) {
        hover = false;
        if (!selected){
            this.setBackground(this.normalColor);
            this.setForeground(this.normalTextColor);
        }
    }
}
           

相當于一個比較基礎的按鈕,實作了滑鼠懸浮,點選變色等,可以繼承做出更好的效果

比較常用的元件

還有一些例如展示商品的card都可以提前做出來,等待調用

/**
*這是一個非常簡單的分割線元件
*/
public class Divider extends JLabel {
    private Color color;
    private int thickness;
    public Divider(Color color, int thickness){
        super();
        this.color = color;
        this.thickness = thickness;
//        this.setBorder(BorderFactory.createLineBorder(color, thickness));
        this.setBorder(new EmptyBorder(thickness,0,0,0));

    }

    public Divider(){
        this(MaterialColors.GRAY_700,1);
    }

    public Divider(int thickness){
        this(MaterialColors.GRAY_700, thickness);
    }

    public Divider(Color color){
        this(color,1);
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D graphics2D = (Graphics2D) g.create();
        graphics2D.setColor(color);
        graphics2D.fillRect(0,0,getWidth(),thickness);
        graphics2D.dispose();
    }
}
           

動畫和繪畫

動畫可以根據Timer定時器來制作,具體可以參見上面的輸入框裡的寫法,動畫效果隻要入門就比較簡單,主要是坐标的計算問題

繪畫也是坐标的計算

//這是一個繼承了FlatButton的元件重寫的繪制方法
    @Override
    public void paint(Graphics g) {

        super.paint(g);
        if (selected){
            Graphics2D graphics2D = (Graphics2D) g.create();
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            GradientPaint paint = new GradientPaint(0, 0, pressedTextColor,0,0,pressedTextColor);
            graphics2D.setPaint(paint);
            graphics2D.fillRect(0,0,5,getHeight());
            GradientPaint paint1 = new GradientPaint(0, 0, normalColor,0,0,normalColor);
            graphics2D.setPaint(paint1);
            graphics2D.drawRoundRect(2,getHeight() / 4,1,1,1,1);
            graphics2D.drawRoundRect(2,getHeight() / 2,1,1,1,1);
            graphics2D.drawRoundRect(2,getHeight() * 3 / 4,1,1,1,1);
            graphics2D.dispose();
        }
    }
           
Java Swing交易市場(C/S模型)結構UI界面連接配接伺服器項目連結
Java Swing交易市場(C/S模型)結構UI界面連接配接伺服器項目連結

上面的代碼可以實作這樣的按鈕效果

界面建構

簡單界面布局

布局 簡介 優點 缺點 坑點
FlowLayout 行布局在一行填滿之後換到下一行 簡單易用 不夠靈活,隻能成行排列,不能以其他方式排列布局,標明方向後所有元件依次排列,不能更改某一個的位置。 在嵌套JScrollPanel時不會自動換行排列
BorderLayout 邊界布局,可以按照五個方向排列元件 可以在五個方向排列元件,已經能應付絕大多數簡單的布局 隻有五個方向,不足以應付所有的布局,其中的元件預設是充滿父元件,盡量的大,尤其是Center元件的填充,容易使布局變醜 如果在add時候不指定BorderLayout左右的元件都會添加到中間,後添加的元件會覆寫之前添加的
GridLayout 表格布局,實作多行多列的排列 在大小相等的多行排列中變現出色 不能調整每一行每一列的寬度,必須等寬,行高也登高,而且會為了填充父布局,拉伸子元件 在元件數量較少的時候會填充整個父元件導緻子元件很大很大
CardLayout 在一個Panel裡同時放多個Panel 便于切換整體布局,比如導航欄對應的部分 如果存放的幾個JPanel的大小不一緻可能會有問題 儲存JPanel的時候如果給它一個名字,點用的時候不要記錯

複雜界面布局(GridBagLayout)

可以說在我寫項目的時候幾乎所有的難以用其他布局完成的布局GridBagLayout都可以輕松完成,但是這個GridBagLayout也比較繁瑣,用法簡單但是代碼比較多

//需要有配套的Constrant
		GridBagLayout gridBagLayout = new GridBagLayout();
        GridBagConstraints gridBagConstraints;
        JPanel contentPanel = new JPanel(gridBagLayout);
        gridBagConstraints = new GridBagConstraints();
        //第幾行
        gridBagConstraints.gridx = 1;
        //第幾列
        gridBagConstraints.gridy = 1;
        //在這一個各自中的位置
        gridBagConstraints.anchor = GridBagConstraints.EAST;
        //占據父容器的比重
        gridBagConstraints.weightx = 0.7;
        //占據子容器的比重
        gridBagConstraints.weighty = 0.7;
        //填充方向,水準,豎直或者所有
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        //還有gridWidth是占據幾行,gridHeight是幾列
        //然後inset是邊距,不推薦使用,但是效果也還可以
        contentPanel.add(new JLabel("test"), gridBagConstraints);
           
Java Swing交易市場(C/S模型)結構UI界面連接配接伺服器項目連結

這是利用GridBagLayout實作的一個界面

每一個元件需要一個GridBagConstraints來限制他的表現,可以限制的大概有以下幾點

  • 元件占據的比重(weightx/weighty)
  • 元件位于的表格(gridx/gridy)
  • 組建的空白(insert)
  • 元件占據幾行幾列,不完全規則的網格布局(gridheight/gridwidth)
  • 元件的對齊方式,支援9個方位,基本完全足夠(anchor)

有幾點需要注意

  • insert不建議使用,會導緻父元件背景色顯示出現問題
  • anchor的固定對齊是相對于某個網格的對齊,不是針對整個頁面
  • anchor如果确實想要利用GridBagLayout将元件放置在比如右下角,在隻有一個元件的情況下需要規定weight和weighty都是1.0或者更大
  • 網格布局的最終依舊保證每一行等高,每一列等寬

連接配接伺服器

Java swing是線程不安全的,所有的更新UI都要在UI線程中完成,或者利用SwingUtil.invokeLater()進行更新。

在連接配接網絡的時候,網絡的延時比較大,需要通過新開線程的方式進行連接配接。在UI線程中連接配接網絡,會造成UI線程的卡頓,在網絡條件很差的情況下,會卡死。

要善于利用線程池,防止同時連接配接數量過大,伺服器過載(畢竟是自家小水管)

servlet連接配接

如果後端用servlet或者是其他方式傳遞資料,傳回Json格式,或者是個人規定好的格式的資料。記住一下幾點:

  • 無論後端給了多好(垃圾)的接口一定要二次封裝,便于以後功能的填充,或者是在明知道後端應該祭天的情況下給自己留一條活路
  • 二次封裝的方法不應是異步方法,或者處理Future資料也可以
  • 一開始的時候要列印出傳回資料,便于調試
//某一個封裝樣例,ResponseEntity是根據後端傳回的資料寫的封裝類
//ConnectControl是連接配接網絡的方法處理後端傳回的資料
public class CommentControl {
    CommentControl(){}
    public ResponseEntity addComment(String token, int lastCommentId, int goodId, String message){
        ConnectControl connectControl = new ConnectControl();
        Map<String, String> map = new HashMap<>();
        map.put("lastCommentId", lastCommentId + "");
        map.put("goodId", goodId + "");
        map.put("message", message);
        connectControl.doPost(BaseValue.load + "addComment", token, map);
        return ResponseEntity.fit(connectControl);
    }

    public ResponseEntity getCommentByGoodId(int goodId){
        ConnectControl connectControl = new ConnectControl();
        Map<String, String> map = new HashMap<>();
        map.put("goodId", goodId + "");
        connectControl.doPost(BaseValue.load + "getCommentByGoodId", null, map);
        return ResponseEntity.fit(connectControl);
    }
}
           

socket

如果要做網絡請求,尤其是類似于C/S模型的聊天socket是必不可少的。實作方法一般是while(true)嵌套socket進行連接配接

  1. 可以在while(true)增加Thread.sleep(100)來緩沖,防止請求過于頻繁
  2. 可以用到上面講的java.awt的Timer定時器來實作定時的效果
public class ChatControl {
    private final static int RETRY_TIME = 5;
    private static int registerCount = 0;
    private static int sendCount = 0;
    private static int receiveCount = 0;
    private static int userId;
    private static Executor fixedThreadPool = Executors.newFixedThreadPool(3);
    private static Socket socket;
    private static DataInputStream inputStream;
    private static DataOutputStream outputStream;
    private static final List<String> messages = new ArrayList<>();
    private static TrayIcon trayIcon;
    static {
        try {
            socket = new Socket(BaseValue.host,BaseValue.chatPort);
            inputStream = new DataInputStream(socket.getInputStream());
            outputStream = new DataOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("socket啟動失敗");
        }
    }

    ChatControl(){
    //這一部分還挺有意思的,是調用系統消息提示,增加托盤圖示
        SystemTray tray = SystemTray.getSystemTray();
        trayIcon = new TrayIcon(Icons.shop.getImage(), "閑置物品交易平台");
        //Let the system resize the image if needed
        trayIcon.setImageAutoSize(true);
        //添加托盤圖示
        trayIcon.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                JFrame f = FrameBase.frameBaseList.get(0);
                f.setVisible(true);
                f.setExtendedState(JFrame.NORMAL);
            }
        });
        try {
            tray.add(trayIcon);
        } catch (AWTException e) {
            e.printStackTrace();
        }
        receive()
    }

    public void send(String message) {
        ResponseEntity responseEntity = new ResponseEntity(0, "",message);
        String json = JSONObject.toJSONString(responseEntity);
        try {
            outputStream.writeUTF(json);
            outputStream.flush();
        } catch (IOException e) {
            if (sendCount == 0){
                e.printStackTrace();
            }
            if (sendCount <= RETRY_TIME){
                System.out.println("sending" + sendCount);
                sendCount++;
                send(message);
            } else {
                System.out.println("嘗試發送失敗");
                sendCount = 0;
            }
        }
    }

    public void receive() {
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try{
                        String result = inputStream.readUTF();
                        JSONObject object = JSONObject.parseObject(result);
                        if (object.getInteger("code") == 0){
                            Stringmessage = JSONObject.parseObject(object.getString("data"), String.class);
                            trayIcon.displayMessage("新消息", message.getText(), TrayIcon.MessageType.NONE);
                            Thread.sleep(200);
                            messages.add(message);
                        }
                    } catch (IOException | InterruptedException e){
                        if (receiveCount == 0){
                            e.printStackTrace();
                        }
                        if (receiveCount <= RETRY_TIME){
                            System.out.println("retry" + receiveCount);
                            receiveCount++;
                            if (receiveCount > RETRY_TIME){
                                System.out.println("socket連接配接丢失");
                                receiveCount = 0;
                                return;
                            }
                            receive();
                        }
                    }
                }
            }
        });
    }

	//外部隻需調用getMessages就能擷取消息
    public List<Message> getMessages(){
        if (messages.size() == 0){
            return new ArrayList<>();
        } else {
            List<String> messageList =  new ArrayList<>(messages);
            messages.clear();
            return messageList;
        }
    }

    public void closeSocket() {
        try {
            socket.close();
            outputStream.close();
            inputStream.close();
        } catch (IOException e){
            e.printStackTrace();
        }

    }

}

           

上面的代碼外部調用隻需要執行個體化之後調用getMesages方法就可以擷取資訊,不用持有外部引用,會降低記憶體洩漏的風險

外部隻要再用Timer定時查詢資料更新ui就可以了

項目連結

Swing C/S交易市場源碼