具有内部字段或预格式化格式的 JTextField,类似于 Windows 中的 ip 字段

2024-02-11

我想创建一个用于日期的文本字段,并且将包含dd.mm.YYYY格式。现在我希望用户只输入数字,而不是点。所以这个字段会是这样的:

_ _. _ _ . _ _ _ _

因此,当用户想要输入日期:例如 15.05.2010 时,他只会输入序列 15052010 中的数字。

我还希望,当他按下向左或向右箭头时,光标从一个字段(不是 JTextField,而是 JTextField 中的字段)转到下一个字段。假设我有 JTextField ,其中包含以下文本:15.05.2010如果用户在开头并按向右箭头,则光标应转到.05 field.

我希望你能理解我,因为现在我不知道如何做到这一点,或者至少不知道如何在谷歌上寻找它。


好吧,这里有 4 个类可以解决您的问题。 就我而言,它的版本控制但 IP 地址具有相同的结构,并且很容易修改它。

package com.demo.textfield.version;

import javax.swing.text.Document;



/**
 * create documents for text fields
 */
public class DocumentsFactory {


private DocumentsFactory() {}



public static Document createIntDocument() {
    return createIntDocument(Integer.MAX_VALUE, Integer.MAX_VALUE);
}


public static Document createIntDocument(int maxValue) {
    return createIntDocument(maxValue, Integer.MAX_VALUE);
}


public static Document createIntDocument(int maxValue, int maxLength) {
    IntDocument intDocument = new IntDocument();
    intDocument.setMaxVal(maxValue);
    intDocument.setMaxLength(maxLength);
    return intDocument;
  }   
}

在下面的类中我们定义视图:

package com.demo.textfield.version;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;

import javax.swing.JPanel;

public class GridbagPanel extends JPanel {

private static final long serialVersionUID = 1L;


public static final Insets NO_INSETS = new Insets(0, 0, 0, 0);  

public GridBagConstraints constraints;
private GridBagLayout layout;


public GridbagPanel() {
    layout = new GridBagLayout();
    constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.NONE;
    constraints.anchor = GridBagConstraints.WEST;       
    constraints.insets = NO_INSETS;
    setLayout(layout);
}

public void setHorizontalFill() {
    constraints.fill = GridBagConstraints.HORIZONTAL;
}

public void setNoneFill() {
    constraints.fill = GridBagConstraints.NONE;
}





public void add(Component component, int x, int y, int width, int
        height, int weightX, int weightY) {
    GridBagLayout gbl = (GridBagLayout) getLayout();

    gbl.setConstraints(component, constraints);

    add(component);
}


public void add(Component component, int x, int y, int width, int height) {
    add(component, x, y, width, height, 0, 0);
}

public void setBothFill() {
    constraints.fill = GridBagConstraints.BOTH;
}

public void setInsets(Insets insets) {
    constraints.insets = insets;

}
}

我们使用包含主要逻辑的普通文档(您的更改应该在这里):

package com.demo.textfield.version;

import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;


/**
 * a class for positive integers
 */
public class IntDocument extends PlainDocument {
    /**
 * 
 */
private static final long serialVersionUID = 1L;

public static final String NUMERIC = "0123456789";

private int maxVal = -1;
private int maxLength = -1;


public IntDocument() {
    this.maxVal = -1;
    maxVal = Integer.MAX_VALUE;
    maxLength = Integer.MAX_VALUE;
}

public void setMaxLength(int maxLength) {
    if (maxLength < 0)
        throw new IllegalArgumentException("maxLength<0");
    this.maxLength = maxLength;
}

public void setMaxVal(int maxVal) {
    this.maxVal = maxVal;
}

public void insertString
        (int offset, String str, AttributeSet attr)
        throws BadLocationException {
    if (str == null)
        return;

    if (str.startsWith(" ") && offset == 0) {
        beep();
        str = "";
    }

    if (!isValidForAcceptedCharsPolicy(str))
        return;

    if (validateLength(offset, str) == false)
        return;

    if (!isValidForMaxVal(offset, str))
        return;


    super.insertString(offset, str, attr);
}

public boolean isValidForAcceptedCharsPolicy(String str) {
    if (str.equals("")) {
        beep();
        return false;
    }

    for (int i = 0; i < str.length(); i++) {
        if (NUMERIC.indexOf(String.valueOf(str.charAt(i))) == -1) {
            beep();
            return false;
        }
    }


    return true;
}


public boolean isValidForMaxVal(int offset, String toAdd) {
    String str_temp;
    //String str_text = "";
    String str1 = "";
    String str2 = "";
    try {
        str1 = getText(0, offset);
        str2 = getText(offset, getLength() - offset);
    } catch (Exception e) {
        e.printStackTrace();
    }

    int i_value;

    str_temp = str1 + toAdd + str2;
    //str_temp = str_temp.trim();

    i_value = Integer.parseInt(str_temp);

    if (i_value > maxVal) {
        beep();
        return false;
    } else
        return true;
}

private boolean validateLength(int offset, String toAdd) {
    String str_temp;
    //String str_text = "";
    String str1 = "";
    String str2 = "";
    try {
        str1 = getText(0, offset);
        str2 = getText(offset, getLength() - offset);
    } catch (Exception e) {
        e.printStackTrace();
    }


    str_temp = str1 + toAdd + str2;
    if (maxLength < str_temp.length()) {
        beep();
        return false;
    } else
        return true;

}


private void beep() {
    //java.awt.Toolkit.getDefaultToolkit().beep();
}


}

这是最后一个主要方法,它实现了上面发布的所有代码,您会得到很好的视图。就我而言,我使用了由点分隔的 4 个文本字段的列表。您可以使用箭头、制表符或点或者如果您的数字长度达到 4,从一个“窗口”跳转到另一个“窗口”。它将与以下实现一起使用FocusAdapter class:

package com.demo.textfield.version;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.FocusManager;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;




/**
* diplays an  version text field
*/
![enter image description here][1]public class VersionTextField extends GridbagPanel {


private static final long serialVersionUID = 1L;

/**
 * a text field for each byte
 */
private JTextField[] textFields;

/**
 * dots between text fields
 */
private JLabel[] dotsLabels;


/**
 * used to calculate enable/disable color; never shown
 */
private static JTextField sampleTextField = new JTextField();

/**
 * listen to changes in the byte fields
 */
private MyDocumentListener documentListener;

/**
 * list of key listeners
 */
private List<KeyListener> keyListenersList;

/**
 * List of Focus Adapter that select all data in JTextFiled during action
 * */
private List<FocusAdapter> focusAdapterList;

/**
 * list of key listeners
 */
private List<FocusListener> focusListenersList;

private int maxHeight = 0;

public VersionTextField() {
    this(4);
}

/**
 * @param byteCount
 *            number of bytes to display
 */
private VersionTextField(int byteCount) {

    textFields = new JTextField[byteCount];
    for (int i = 0; i < textFields.length; i++) {
        textFields[i] = new JTextField(4);

    }

    //layout
    //constraints.insets = new Insets(0, 0, 0, 0);

    List<JLabel> dotsLabelsList = new ArrayList<JLabel>();

    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.setHorizontalAlignment(JTextField.CENTER);
        Document document = DocumentsFactory.createIntDocument(9999);
        textField.setDocument(document);

        if (i < textFields.length-1) {
            add(textField, i * 2, 0, 1, 1);
            if (textField.getPreferredSize().height > maxHeight)
                maxHeight = textField.getPreferredSize().height;
            JLabel label = new JLabel(".");



            add(label, (i * 2) + 1, 0, 1, 1);
            if (label.getPreferredSize().height > maxHeight)
                maxHeight = label.getPreferredSize().height;
            dotsLabelsList.add(label);
        } else
            add(textField, i * 2, 0, 1, 1);

    }

    //dotsLabels = new JLabel[dotsLabelsList.size()];
    dotsLabels = new JLabel[dotsLabelsList.size()];


    dotsLabels = dotsLabelsList.toArray(dotsLabels);

    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.setBorder(BorderFactory.createEmptyBorder());
    }

    //init
    Color backgroundColor = UIManager.getColor("TextField.background");
    setBackground(backgroundColor);
    Border border = UIManager.getBorder("TextField.border");
    setBorder(border);

    //register listeners
    for (int i = 1; i < textFields.length; i++) {
        JTextField field = textFields[i];
        field.addKeyListener(new BackKeyAdapter());
    }

    documentListener = new MyDocumentListener();
    for (int i = 0; i < textFields.length - 1; i++) {
        JTextField field = textFields[i];
        field.getDocument().addDocumentListener(documentListener);
        field.addKeyListener(new ForwardKeyAdapter());
    }

    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.addKeyListener(new MyKeyListener());
    }

    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.addFocusListener(new MyFocusAdapter());
    }

    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.addFocusListener(new MyFocusAdapter());
    }

    keyListenersList = new ArrayList<KeyListener>();
    focusListenersList = new ArrayList<FocusListener>();
    focusAdapterList = new ArrayList<FocusAdapter>();
}

public synchronized void addKeyListener(KeyListener l) {
    super.addKeyListener(l);
    keyListenersList.add(l);
}

public synchronized void addFocusListener(FocusListener l) {
    super.addFocusListener(l);
    if (focusListenersList != null)
        focusListenersList.add(l);
}

public synchronized void removeKeyListener(KeyListener l) {
    super.removeKeyListener(l);
    if (focusListenersList != null)
        keyListenersList.remove(l);
}

public synchronized void removeFocusListener(FocusListener l) {
    super.removeFocusListener(l);
    keyListenersList.remove(l);
}

public void setEnabled(boolean b) {
    super.setEnabled(b);
    sampleTextField.setEnabled(b);
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.setEnabled(b);
    }

    for (int i = 0; i < dotsLabels.length; i++) {
        JLabel dotsLabel = dotsLabels[i];           
        dotsLabel.setEnabled(b);
    }

    setBackground(sampleTextField.getBackground());
    setForeground(sampleTextField.getForeground());
    setBorder(sampleTextField.getBorder());

}

public void requestFocus() {
    super.requestFocus();
    textFields[0].requestFocus();
}

public void setEditable(boolean b) {
    sampleTextField.setEditable(b);
    setBackground(sampleTextField.getBackground());
    setForeground(sampleTextField.getForeground());
    setBorder(sampleTextField.getBorder());
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.setEditable(b);
    }

    for (int i = 0; i < dotsLabels.length; i++) {
        JLabel dotsLabel = dotsLabels[i];


        dotsLabel.setForeground(sampleTextField.getForeground());
    }
}

public boolean isFieldEmpty() {
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        String sCell = textField.getText().trim();
        if (!(sCell.equals("")))
            return false;
    }
    return true;
}




public Dimension getPreferredSize() {
    if (super.getPreferredSize().height > maxHeight)
        maxHeight = super.getPreferredSize().height;
    return new Dimension(super.getPreferredSize().width, maxHeight);
}





/**
 * clears current text in text fiekd
 */
private void reset() {
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.getDocument().removeDocumentListener(documentListener);
        textField.setText("");
        textField.getDocument().addDocumentListener(documentListener);
    }
}



public static void main(String[] args) {

    JFrame frame = new JFrame("test");
    VersionTextField ipTextField = new VersionTextField();
    ipTextField.setText("9.1.23.1479");
    frame.getContentPane().setLayout(new FlowLayout());
    frame.getContentPane().add(ipTextField);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
}


public void setText(String version) {
    if (version == null || "".equals(version) || "null".equals(version))
        reset();
    else {           
        setVer(version.split("[.]"));
    }
}


private void setVer(String[] ver) {
    if (ver == null) {
        reset();
        return;
    }

    Enumeration<String> enumeration =  Collections.enumeration(Arrays.asList(ver));
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        String s = (String) enumeration.nextElement();
        textField.getDocument().removeDocumentListener(documentListener);
        textField.setText(s);
        textField.getDocument().addDocumentListener(documentListener);
    }
}

public void setToolTipText(String toolTipText) {
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];
        textField.setToolTipText(toolTipText);
    }
}



private class MyDocumentListener implements DocumentListener {


    @Override
    public void insertUpdate(DocumentEvent e) {
        Document document = e.getDocument();
        try {
            JTextField textField = (JTextField) FocusManager.getCurrentManager().getFocusOwner();

            String s = document.getText(0, document.getLength());

            if (s.length() == 4){ // && textField.getCaretPosition() == 2) {
                textField.transferFocus();


            }

        } catch (BadLocationException e1) {
            e1.printStackTrace();
            return;
        }

    }

    public void removeUpdate(DocumentEvent e) {
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        //          Document document = e.getDocument();
        //          try {
        //              Component component = FocusManager.getCurrentManager().getFocusOwner();
        //              String s = document.getText(0, document.getLength());
        //              
        //              // get selected integer
        //              int valueInt = Integer.parseInt(s);
        //              
        //              if (valueInt > 25) {
        //                  component.transferFocus();
        //              }
        //
        //          } catch (BadLocationException e1) {
        //              e1.printStackTrace();
        //              return;
        //          }
    }
}

private class BackKeyAdapter extends KeyAdapter {

    public void keyPressed(KeyEvent e) {
        JTextField textField = (JTextField) e.getComponent();
        if (textField.getCaretPosition() == 0
                && KeyEvent.VK_LEFT == e.getKeyCode()
                && e.getModifiers() == 0)
            textField.transferFocusBackward();
        if (textField.getCaretPosition() == 0
                && KeyEvent.VK_BACK_SPACE == e.getKeyCode()
                && e.getModifiers() == 0) {
            textField.transferFocusBackward();
        }
    }
}

private class ForwardKeyAdapter extends KeyAdapter {
    public void keyPressed(KeyEvent e) {
        JTextField textField = (JTextField) e.getComponent();

        if (KeyEvent.VK_RIGHT == e.getKeyCode() && e.getModifiers() == 0) {
            int length = textField.getText().length();
            int caretPosition = textField.getCaretPosition();

            if (caretPosition == length) {
                textField.transferFocus();
                e.consume();
            }
        }
        if (e.getKeyChar() == '.' &&
                textField.getText().trim().length() != 0) {
            textField.setText(textField.getText().trim());
            textField.transferFocus();
            e.consume();
        }
    }
}

/**
 * @return current text in ip text field
 */
public String getText()  {
    StringBuffer buffer = new StringBuffer();
    String ipResult;
    for (int i = 0; i < textFields.length; i++) {
        JTextField textField = textFields[i];

        if(textField.getText().trim().equals("")){
            return "";
        }

        buffer.append(Integer.parseInt(textField.getText()));
        if (i < textFields.length - 1){
            buffer.append('.');
        }
    }
    ipResult = buffer.toString();       

    return ipResult;
}

/**
 * general purpose key listener
 */
private class MyKeyListener implements KeyListener {
    public void keyPressed(KeyEvent e) {
        for (int i = 0; i < keyListenersList.size(); i++) {
            KeyListener keyListener = keyListenersList.get(i);
            keyListener.keyPressed(new KeyEvent(VersionTextField.this,
                    e.getID(), e.getWhen(), e.getModifiers(), e
                    .getKeyCode(), e.getKeyChar(), e
                    .getKeyLocation()));
        }
    }

    public void keyReleased(KeyEvent e) {
        for (int i = 0; i < keyListenersList.size(); i++) {
            KeyListener keyListener = keyListenersList.get(i);
            keyListener.keyReleased(new KeyEvent(VersionTextField.this, e
                    .getID(), e.getWhen(), e.getModifiers(),
                    e.getKeyCode(), e.getKeyChar(), e.getKeyLocation()));
        }
    }

    public void keyTyped(KeyEvent e) {
        for (int i = 0; i < keyListenersList.size(); i++) {
            KeyListener keyListener = keyListenersList.get(i);
            keyListener.keyTyped(new KeyEvent(VersionTextField.this, e.getID(),
                    e.getWhen(), e.getModifiers(), e.getKeyCode(), e
                    .getKeyChar(), e.getKeyLocation()));
        }
    }
}

private class MyFocusAdapter extends FocusAdapter {

    public void focusGained(FocusEvent e) {
        for (int i = 0; i < focusListenersList.size(); i++) {
            FocusListener focusListener = focusListenersList.get(i);
            focusListener.focusGained(new FocusEvent(
                                                    VersionTextField.this,
                                                    e.getID(),
                                                    e.isTemporary(),
                                                    e.getOppositeComponent()
                                                    ));
        }

    if(e.getComponent() instanceof javax.swing.JTextField){
        highlightText((JTextField)e.getSource());
    }


    }           

    public void focusLost(FocusEvent e) {
        for (int i = 0; i < focusListenersList.size(); i++) {
            FocusListener focusListener = focusListenersList.get(i);
            focusListener.focusLost(new FocusEvent(
                                                    VersionTextField.this,
                                                    e.getID(),
                                                    e.isTemporary(),
                                                    e.getOppositeComponent()
                                                    ));
        }
    }

    public void highlightText(javax.swing.JTextField ctr){
        //ctr.setSelectionColor(Color.BLUE);
        //ctr.setSelectedTextColor(Color.WHITE);
        ctr.setSelectionStart(0);
        ctr.setSelectionEnd(ctr.getText().length());            
        System.out.println(ctr.getText());

 }
}


}

看看我们得到了什么:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

具有内部字段或预格式化格式的 JTextField,类似于 Windows 中的 ip 字段 的相关文章

  • Firebase 查询 Or'ing whereEqualTo 以获得可能值的列表

    我见过之前针对早期版本的 Firebase 提出过这个问题 https stackoverflow com questions 26700924 query based on multiple where clauses in fireba
  • Hibernate OneToMany 关系是 PersistentBag 而不是 List

    我正在 javafx 中开发一个应用程序 它通过 RMI 与 EAR 连接 该 EAR 连接到 SQLServer DB 并使用 hibernate 映射 POJOS 这些 POJOS 包含双向 OneToMany 和 ManyToOne
  • V8 如何管理它的堆?

    我知道V8的垃圾收集在工作时 会从GC的root开始追踪 这样无法到达的对象就会被标记然后被清除 我的问题是GC是如何遍历那些对象的 必须有一个数据结构来存储所有可达或不可达的对象 位图 链接表 顺便说一句 JVM 也做同样的事情吗 艾伦秀
  • 以编程方式将 PEM 证书导入 Java KeyStore

    我有一个由两个文件 crt 和 key 组成的客户端证书 我希望将其导入到 java KeyStore 中 然后在 SSLContext 中使用 以通过 Apache 的 HTTPClient 发送 HTTP 请求 但是 我似乎找不到一种以
  • Spring webflow 应用程序:HTTP 302 暂时移动

    我的 java 应用程序中的每个请求都会生成另外 2 个带有 HTTP 302 错误的请求 例如 如果请求查看名为板 html 这个请求是从首页 html 我收到按以下顺序生成的 3 个请求 POST home html 302 Moved
  • 有效地查找正则表达式的所有重叠匹配项

    这是后续与 java 正则表达式匹配的所有重叠子字符串 https stackoverflow com q 11303309 244526 有没有办法让这段代码更快 public static void allMatches String
  • 从 eclipse 运行时 java.io.FileNotFoundException: (没有这样的文件或目录)

    我正在写入文件并想要控制台输出 TODO Create a game engine and call the runGame method public static void main String args throws Excepti
  • 业务代表与服务定位器

    Business Delegate 和 Service Locator 之间有什么区别 两者都负责封装查找和创建机制 如果 Business Delegate 使用 Service Locator 来隐藏查找和创建机制 那么 Busines
  • 我们可以在三元运算符(Java)中使用命令吗?

    这是一个工作代码 String a first String b second String object System out println object null a b 但它不是 String a first String b se
  • 独占锁定ConcurrentHashMap

    我知道不可能锁定 ConcurrentHashMap 进行独占访问 但是 我找不到原因 是因为构成CHM的 Segment 没有被api公开吗 据推测 如果是的话 客户端代码可以执行 交接 锁定 Cheers 我知道不可能锁定 Concur
  • 您能让 Tomcat 6 stdout.log 文件表现得像 log4j DailyRollingFileAppender 吗?

    我们使用的是 Tomcat 6 的 Windows 安装 默认情况下 我们应用程序的 log4j 输出将转到 catalina base logs stdout log 文件 该日志文件仅在我们重新启动 Tomcat 时滚动 并且文件名始终
  • java JFileChooser 文件大小过滤器

    我知道我可以按文件类型进行过滤 但是可以按文件大小进行过滤吗 例如 JFileChooser 仅显示 3 MB 以内的图片 简短的回答应该是 你尝试过什么 长答案是肯定的 JFileChooser fc new JFileChooser f
  • 在 Mac 上使用 JRE 打开 jar 文件

    我有一个 jar 文件 旨在通过命令行运行 我不打算在运行应用程序的机器上进行任何java开发 我的思考过程是 因此我应该只需要JRE而不是JDK 此外 JDK 大约是 JRE 的 4 倍 我不想下载它 在 Mac 上安装 JRE 时 它不
  • Java中的DRY原则[关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我一直在读关于DRY https en wikipedia org wiki Don 27t repeat yourself原则 虽然看起来
  • java中的比较器链

    正在阅读Oracle 关于接口的 Java 教程 https docs oracle com javase tutorial java IandI createinterface html其中给出了一个例子Card 打牌 我试图理解接口中的
  • Scala repl 抛出错误

    当我打字时scala在终端上启动 repl 它会抛出此错误 scala gt init error error while loading AnnotatedElement class file usr lib jvm java 8 ora
  • Android 中的字符串加密

    我正在使用代码进行加密和加密 它没有给出字符串结果 字节数组未转换为字符串 我几乎尝试了所有方法将字节数组转换为字符 但没有给出结果 public class EncryptionTest extends Activity EditText
  • Java的hashCode可以为不同的字符串产生相同的值吗?

    使用java的哈希码函数是否可以为不同的字符串提供相同的哈希码 或者如果可能的话 其可能性的 是多少 Java 哈希码是 32 位 它散列的可能字符串的数量是无限的 所以是的 会发生冲突 百分比是没有意义的 项目 字符串 的数量是无限的 而
  • 日期时间解析异常

    解析日期时 我的代码中不断出现异常错误 日期看起来像这样 Wed May 21 00 00 00 EDT 2008 这是尝试读取它的代码 DateTimeFormatter formatter DateTimeFormatter ofPat
  • 使用 Android 的 Mobile Vision API 扫描二维码

    我跟着这个tutorial http code tutsplus com tutorials reading qr codes using the mobile vision api cms 24680关于如何构建可以扫描二维码的 Andr

随机推荐