Home > android, ui > An EditText for entering IP addresses

An EditText for entering IP addresses

So I needed to create a custom EditText for entering IP addresses. I wanted it to do two things: one, display the numpad-style IME, and two, accept only digits and the dot character.

There is no pre-defined EditText input type for this, and although using android:digits="0123456789." comes pretty close, but doesn’t quite do it for me, since it doesn’t switch the IME into the numpad mode.

As it turns out, implementing special rules for IP address entry is not too difficult, it just takes a special KeyListener.

The docs say that KeyListener is an interface for converting text key events into edit operations on an Editable class.

There are a bunch of pre-defined KeyListener subclasses, which are accessible to SDK applications, and we’re going to use one of them as the base class for our IPAddressKeyListener.

Associating an EditText with our key listener is done like this:

	EditText edAddress = (EditText) findViewById(R.id.advanced_network_address);
	edAddress.setKeyListener(IPAddressKeyListener.getInstance());

Almost ready to show the code. Just a quick reminder on what this class gives us:

  • Switches the on-screen keyboard (the IME) to numpad mode
  • Only allows digits 0 through 9 and the dot character
  • The first character has to be a digit (not a dot)
  • At most, 3 dot characters are allowed

The code:

public class IPAddressKeyListener extends NumberKeyListener {

	private char[] mAccepted;
	private static IPAddressKeyListener sInstance;

	@Override
	protected char[] getAcceptedChars() {
		return mAccepted;
	}

	/**
	 * The characters that are used.
	 * 
	 * @see KeyEvent#getMatch
	 * @see #getAcceptedChars
	 */
	private static final char[] CHARACTERS =

	new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' };

	private IPAddressKeyListener() {
		mAccepted = CHARACTERS;
	}

	/**
	 * Returns a IPAddressKeyListener that accepts the digits 0 through 9, plus the dot
	 * character, subject to IP address rules: the first character has to be a digit, and
	 * no more than 3 dots are allowed.
	 */
	public static IPAddressKeyListener getInstance() {
		if (sInstance != null) return sInstance;

		sInstance = new IPAddressKeyListener();
		return sInstance;
	}

	/**
	 * Display a number-only soft keyboard.
	 */
	public int getInputType() {
		return InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL;
	}

	/**
	 * Filter out unacceptable dot characters.
	 */
	@Override
	public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart,
			int dend) {
		CharSequence out = super.filter(source, start, end, dest, dstart, dend);

		if (out != null) {
			source = out;
			start = 0;
			end = out.length();
		}

		int decimal = -1;
		int dlen = dest.length();

		// Prevent two dot characters in a row
		if (dstart > 0 && dest.charAt(dstart - 1) == '.') {
			decimal = dstart - 1;
		}
		if (dend < dlen && dest.charAt(dend) == '.') {
			decimal = dend;
		}

		// Up to three dot charcters, and no more
		if (decimal == -1) {
			int decimalCount = 0;
			for (int i = 0; i < dstart; i++) {
				char c = dest.charAt(i);

				if (c == '.') {
					decimalCount++;
					decimal = i;
				}
			}
			for (int i = dend; i < dlen; i++) {
				char c = dest.charAt(i);

				if (c == '.') {
					decimalCount++;
					decimal = i;
				}
			}

			if (decimalCount < 3) {
				decimal = -1;
			}
		}

		SpannableStringBuilder stripped = null;

		for (int i = end - 1; i >= start; i--) {
			char c = source.charAt(i);
			boolean strip = false;

			if (c == '.') {
				if (i == start && dstart == 0) {
					strip = true;
				} else if (decimal >= 0) {
					strip = true;
				} else {
					decimal = i;
				}
			}

			if (strip) {
				if (end == start + 1) {
					return ""; // Only one character, and it was stripped.
				}

				if (stripped == null) {
					stripped = new SpannableStringBuilder(source, start, end);
				}

				stripped.delete(i - start, i + 1 - start);
			}
		}

		if (stripped != null) {
			return stripped;
		} else if (out != null) {
			return out;
		} else {
			return null;
		}
	}
}

Some additional validation is still necessary (like checking that individual byte values are less than or equal to 255), but that can be done when the user leaves the edit control.

Advertisements
Categories: android, ui
  1. Scott
    January 2, 2014 at 5:46 pm

    Ok, I give up… it did it again!

    • January 2, 2014 at 8:43 pm

      Scott, thank you. I believe WordPress filters out / replaces anything which looks like an HTML tag. Let me try wrapping it in markup:

      public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
      int dstart,int dend) {
      
      if (end > start) {
      String destTxt = dest.toString();
      String resultingTxt = destTxt.substring(0, dstart)
      + source.subSequence(start, end)
      + destTxt.substring(dend);
      if (!resultingTxt.matches(“^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?”)) {
      return “”;
      } else {
      String[] splits = resultingTxt.split(“\\.”);
      for (int i=0; i 255){
      return “”;
      }
      }
      }
      }
      return null;
      }
      
  2. Mario
    September 8, 2013 at 9:48 pm

    good starting point, but with below code the filter check also that single ip part doesn’t exceed the 255

    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {

    if (end > start) {
    String destTxt = dest.toString();
    String resultingTxt = destTxt.substring(0, dstart)
    + source.subSequence(start, end)
    + destTxt.substring(dend);
    if (!resultingTxt.matches(“^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?”)) {
    return “”;
    } else {
    String[] splits = resultingTxt.split(“\\.”);
    for (int i = 0; i 255) {
    return “”;
    }
    }
    }
    }
    return null;
    }

    • September 8, 2013 at 10:07 pm

      Very good. This though looks like a copy paste error:

      for (int i = 0; i 255) {

    • Scott
      January 2, 2014 at 5:38 pm

      Great, been looking for something like this but there seems to be some code missing from the reply above. Not sure what it should have been but this works, just added the for loop at the end.

      public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
      int dstart,int dend) {

      if (end > start) {
      String destTxt = dest.toString();
      String resultingTxt = destTxt.substring(0, dstart)
      + source.subSequence(start, end)
      + destTxt.substring(dend);
      if (!resultingTxt.matches(“^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?”)) {
      return “”;
      } else {
      String[] splits = resultingTxt.split(“\\.”);
      for (int i=0; i 255){
      return “”;
      }
      }
      }
      }
      return null;
      }

      It just stops the user entering a number bigger than 255 in any part of the IP address.

      • Scott
        January 2, 2014 at 5:43 pm

        Thats odd… I just replied with updated code and it has changed to exactly the same as the original reply with the same lines missing!
        For loop at the end should be:

        for (int i=0; i 255){
        return “”;
        }
        }

  3. ram.ru
    May 11, 2012 at 1:16 pm

    Using the following works too, however the above code is a better solution:
    android:inputType=”numberDecimal”
    android:digits=”0123456789.”

    • May 11, 2012 at 3:12 pm

      Just using android:inputType and android:digits without an input filter will allow input like “12345…..67890”. Doesn’t look like an IP address to me.

      Now, because of extracted keyboard mode, it’s necessary to do post-input validation anyway, so… I guess it’s not that big of a deal.

  4. shema
    December 5, 2011 at 1:07 am

    Great! Very helpful, thank you!

  1. January 17, 2016 at 10:40 am
  2. August 25, 2011 at 5:21 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s