Android - getButtonDrawable for CompoundButton

   Android UI Toolkit's widgets often have helper methods to get the values that are or have been set via xml or runtime code etc. ImageButton has helper APIs to set and get drawable via setImageDrawable and getDrawable. This facilitates run time updates to previously set drawable and could be done via Canvas and Paint. Few applications might want to redraw the image like adding blur effects based on some run time conditions. Unfortunately, the ability to extract previously set drawable resource isn't available across all widgets. CompoundButton is one such widget which has a setButtonDrawable but has a getButtonDrawable only from API level 23.

   Application developers targeting previous android versions had to workaround by hand picking platform default resources into their application project and keeping a reference of the drawable for further updates. This wouldn't always work especially when OEMs decide to change the default images for the CompoundButton like radio button or check box (highly unlikely but still a possibility). Fortunately, support library developers sensed the need and provided a helper API CompoundButtonCompat offering a static helper API to get the drawable resource (starting 23.0.1). So how did the support library developers manage to solve this in application process space (sandboxed model) that application developers couldn't or had to workaround.

   It turns out that CompoundButtonCompat just relays the call to an appropriate helper based on the platform version and BaseCompoundButtonCompat (applicable for pre-23) uses Java's reflection capability to fetch the actual drawable resource. As a side note, support version 23.0.1 having CompoundButtonCompat has a regression in Loader. Loaders aren't restarted upon orientation change. Folks looking for CompoundButtonCompat can instead just fork the support library version (Apache license) without having to upgrade support library.

    static {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk >= 23) {
            IMPL = new Api23CompoundButtonImpl();
        } else if (sdk >= 21) {
            IMPL = new LollipopCompoundButtonImpl();
        } else {
            IMPL = new BaseCompoundButtonCompat();
        }

    }

    static Drawable getButtonDrawable(CompoundButton button) {

        if (!sButtonDrawableFieldFetched) {
            try {
                sButtonDrawableField = CompoundButton.class.getDeclaredField("mButtonDrawable");
                sButtonDrawableField.setAccessible(true);
            } catch (NoSuchFieldException e) {
                Log.i(TAG, "Failed to retrieve mButtonDrawable field", e);
            }
            sButtonDrawableFieldFetched = true;
        }

        if (sButtonDrawableField != null) {
            try {
                return (Drawable) sButtonDrawableField.get(button);
            } catch (IllegalAccessException e) {
                Log.i(TAG, "Failed to get button drawable via reflection", e);
                sButtonDrawableField = null;
            }
        }
        return null;

    }

No comments: