C/S结构进行开发,将客户端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();
}
}
布局 | 简介 | 优点 | 缺点 | 坑点 |
---|---|---|---|---|
FlowLayout | 行布局在一行填满之后换到下一行 | 简单易用 | 不够灵活,只能成行排列,不能以其他方式排列布局,选定方向后所有组件依次排列,不能更改某一个的位置。 | 在嵌套JScrollPanel时不会自动换行排列 |
BorderLayout | 边界布局,可以按照五个方向排列组件 | 可以在五个方向排列组件,已经能应付绝大多数简单的布局 | 只有五个方向,不足以应付所有的布局,其中的组件默认是充满父组件,尽量的大,尤其是Center组件的填充,容易使布局变丑 | 如果在add时候不指定BorderLayout左右的组件都会添加到中间,后添加的组件会覆盖之前添加的 |
GridLayout | 表格布局,实现多行多列的排列 | 在大小相等的多行排列中变现出色 | 不能调整每一行每一列的宽度,必须等宽,行高也登高,而且会为了填充父布局,拉伸子组件 | 在组件数量较少的时候会填充整个父组件导致子组件很大很大 |
CardLayout | 在一个Panel里同时放多个Panel | 便于切换整体布局,比如导航栏对应的部分 | 如果存放的几个JPanel的大小不一致可能会有问题 | 储存JPanel的时候如果给它一个名字,点用的时候不要记错 |
可以说在我写项目的时候几乎所有的难以用其他布局完成的布局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);
这是利用GridBagLayout实现的一个界面
每一个组件需要一个GridBagConstraints来约束他的表现,可以约束的大概有以下几点
有几点需要注意
Java swing是线程不安全的,所有的更新UI都要在UI线程中完成,或者利用SwingUtil.invokeLater()进行更新。
在连接网络的时候,网络的延时比较大,需要通过新开线程的方式进行连接。在UI线程中连接网络,会造成UI线程的卡顿,在网络条件很差的情况下,会卡死。
要善于利用线程池,防止同时连接数量过大,服务器过载(毕竟是自家小水管)
如果后端用servlet或者是其他方式传递数据,返回Json格式,或者是个人规定好的格式的数据。记住一下几点:
//某一个封装样例,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);
}
}
如果要做网络请求,尤其是类似于C/S模型的聊天socket是必不可少的。实现方法一般是while(true)嵌套socket进行连接
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就可以了