Saturday, February 16, 2013

Subclassing Spinner and ArrayAdapter to change drop-down appearance

As we know, applying different layout can change text color and size, background color and so on. There is no need to do any Java code to achieve that, we just use existing Android classes. Now if we want to change drop-down item and include there more controls than just TextView and one drawable or maybe to apply custom resizing, than we have to dive into ArrayAdapter source and write custom adapter. I will not bother you with image and two TextViews, there is plenty of tutorials on Web about that, instead I will just try to resize drop-down to keep it simple. So here is complete code for minimalistic custom adapter:

public class CustomAdapter extends ArrayAdapter implements SpinnerAdapter{
    private LayoutInflater mInflater;
    private int mFieldId = 0;
    private int mResource;
    public CustomAdapter(Context context, int textViewResourceId,
            String[] objects) {
        super(context, textViewResourceId, objects);
        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(position, convertView, parent, mResource);
    }
    @Override
    public void setDropDownViewResource(int resource) {
        this.mResource = resource;
    }
    private View createViewFromResource(int position, View convertView, ViewGroup parent,
            int resource) {
        View view;
        TextView text;
        if (convertView == null) {
            view = mInflater.inflate(resource, parent, false);
        } else {
            view = convertView;
        }
        try {
            if (mFieldId  == 0) {
                //  If no custom field is assigned, assume the whole resource is a TextView
                text = (TextView) view;
            } else {
                //  Otherwise, find the TextView field within the layout
                text = (TextView) view.findViewById(mFieldId);
            }
        } catch (ClassCastException e) {
            Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
            throw new IllegalStateException(
                    "ArrayAdapter requires the resource ID to be a TextView", e);
        }
        String item = getItem(position);
        if (item instanceof CharSequence) {
            text.setText((CharSequence)item);
        } else {
            text.setText(item.toString());
        }
        view.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
        return view;
    }
}


Most of createViewFromResource is Android code from ArrayAdapter. My modest contribution is only red line. If I specify there some values for width, instead of LayoutParams.FILL_PARENT then I will achieve this:


Well, items are narrower but container is still the same, so subclassing ArrayAdapter doesn’t really help. What needs to be resized and repositioned is AlertDialog which is holding those rows. We can find that out when we open Spinner source. Now I will again do minimalistic subclassing of Spinner.

public class CustomSpinner extends Spinner {
    private AlertDialog mPopup;
    public CustomSpinner(Context context) {
        super(context);
    }
    public CustomSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mPopup != null && mPopup.isShowing()) {
            mPopup.dismiss();
            mPopup = null;
        }
    }
    @Override
    public boolean performClick() {
        Context context = getContext();

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        CharSequence prompt = getPrompt();
        if (prompt != null) {
            builder.setTitle(prompt);
        }
        mPopup = builder.setSingleChoiceItems(
                new DropDownAdapter(getAdapter()), getSelectedItemPosition(),
                this).show();
        WindowManager.LayoutParams layout = mPopup.getWindow().getAttributes();
        layout.x = -128;
        layout.y = -110;
        layout.height = 320;
        layout.width = 240;
        mPopup.getWindow().setAttributes(layout);
        return true;
    }
    @Override
    public void onClick(DialogInterface dialog, int which) {
        setSelection(which);
        dialog.dismiss();
        mPopup = null;
    }
    /*
     * here you copy and paste code for DropDownAdapter from Spinner
     */
}


Hardcoded values are good enough for illustration of subclassing. Now since we are using custom control we need to change tag in layout:


Now if we build and run application in emulator we will get this:



Not very complicated.

No comments:

Post a Comment