Custom Android pin code entry widget

android, widget

Solution

I've made considerable progress on this and no longer need help with the InputConnection. In short, you extend `BaseInputConnection` and override `getEditable()` to return an Editable. In this case, I'm returning a private `TextView` used internally by the `PinCodeView`. `PinCodeView` is also overriding several methods like `onKey[foo]()` and forwarding the call to the internal `TextView`. The rest is just using a `TextWatcher` to update one of the child `ImageView`s whenever the text changes.

It works really well. There are still a few minor issues left to polish it up, but I'll address those as separate questions and close this here.

Problem

I am trying to create a custom pin code widget for android as an alternative to just using an `EditText` with a password inputType attribute. What I'd like to display is a row of boxes, and have each box be filled as the user types his pin. Someone else did something like this but it turned out to be a fixed number of `EditText` views and there was a lot of ugly code for swapping focus as characters were typed or deleted. This is NOT the approach I want to take; rather, I'm designing mine to have customizable length (easy) and behave as a single focusable view (not so easy). My concept thus far is some kind of hybrid between a `LinearLayout` (to hold the "boxes") and an `EditText` (to store the user's input). This is the code so far... ``` public class PinCodeView extends LinearLayout { protected static final int MAX_PIN_LENGTH = 10; protected static final int MIN_PIN_LENGTH = 1; protected int pinLength; protected EditText mText; public PinCodeView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PinCodeView); try { pinLength = a.getInt(R.styleable.PinCodeView_pinLength, 0); } finally { a.recycle(); } pinLength = Math.min(pinLength, MAX_PIN_LENGTH); pinLength = Math.max(pinLength, MIN_PIN_LENGTH); setupViews(); Log.d(TAG, "PinCodeView initialized with pinLength = " + pinLength); } private void setupViews() { LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); for (int i = 0; i < pinLength; i++) { // inflate an ImageView and add it View child = inflater.inflate(R.layout.pin_box, null, false); addView(child); } } public CharSequence getText() { // TODO return pin code text instead return null; } public int length() { // TODO return length of typed pin instead return pinLength; } @Override public boolean onCheckIsTextEditor() { return true; } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { // TODO return an InputConnection return null; } } ``` About those overrides: onCheckIsTextEditor() should return true and onCreateInputConnection(EditorInfo outAttrs) should return a new `InputConnection` object to interact with an InputMethod (a keyboard), but that's all I know. Does anyone know if I'm on the right track? Has anyone done work with `InputConnection` before or made their own editable views able to give guidance? (Edit 1) After looking at this some more, it seems I should subclass BaseInputConnection and supply a `TextView` or `EditText` as its target: ``` @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (!onCheckIsTextEditor()) { return null; } return new BaseInputConnection(mText, true); } ``` Assuming this does store the text as it is typed, I still need some way to update the views to reflect the content change... (Edit 2) So I added this custom view to a screen for testing. It shows the number of boxes, and the whole view is focusable, but the keyboard never pops up. I know it gains/loses focus because the boxes show highlighting appropriately and I set an `OnFocusChangedListener` to write to logcat. What makes an actual keyboard appear when an editable view takes focus?

Original source