Autosizing TextView

In order to use Android O+ autosizing functionality in the TextView (also using AppCompat or AndroidX versions) there is a small important thing that needs to be taken into account: both dimensions must be constrained to a fixed size.

This means that wrap_content cannot be used in any of the dimensions. One could thought that having one of the dimensions fixed (e.g. the width) and the other one using wrap_content will use the constrained dimension to determine the scale of the text, and then the unconstrained (wrap_content) will match the calculated height, but that’s not the case: TextView tries to fit inside both dimensions always, so if one of them is unconstrained the result can range from text not being scaled (see this StackOverflow answer), to totally undefined (see Android Developer documentation). Specifically:

Note: If you set autosizing in an XML file, it is not recommended to use the value “wrap_content” for the layout_width or layout_height attributes of a TextView. It may produce unexpected results.

Layout inflation and why AppCompat components replace standard ones

Some time ago I saw the following question in StackOverflow: Why does Android select AppCompat components when I don’t declare them explicitly?

I was debugging my App and found while hovering on an ImageView reference that, it is an AppCompatImageView instead of an ImageView. The same happened with a TextView(with AppCompatTextView).

So, why Android Views get “automagically” converted into AppCompat* equivalents? The answer comes from the fact that he is extending from AppCompatActivity instead of Activity or other alternatives (in fact, the original question also asks whether he should keep extending AppCompatActivity or start using Activity).

Short answer to “Should I just extend from an Activity from now on?” is no, you should keep extending AppCompatActivity as it provides backwards compatible features to older devices. In the case of AppCompatImageView:

A ImageView which supports compatible features on older versions of the platform, including:

  • Allows dynamic tint of its background via the background tint methods in ViewCompat.
  • Allows setting of the background tint using backgroundTint and backgroundTintMode.
  • Allows dynamic tint of its image via the image tint methods in ImageViewCompat.
  • Allows setting of the image tint using tint and tintMode.

Also, it adds compatibility with vector drawables for older Android versions.

So, how when the layout specifies ImageView, an AppCompatImageView gets created?

AppCompatActivity installs a LayoutInflater.Factory2 to intercept the inflation of certain views. The code of this inflater can be seen in

The function responsible for creating the Views is AppCompatViewInflater#createView(View, String, Context, AttributeSet, boolean, boolean, boolean, boolean), and as you can see here it checks for simple view names (without the package prefixing it), and creates the AppCompat* version instead:

public final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    final Context originalContext = context;

    // ...

    View view = null;

    // We need to 'inject' our tint aware Views in place of the standard framework versions
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
        case "Button":
            view = new AppCompatButton(context, attrs);
        case "EditText":
            view = new AppCompatEditText(context, attrs);
        case "Spinner":
            view = new AppCompatSpinner(context, attrs);
        case "ImageButton":
            view = new AppCompatImageButton(context, attrs);
        case "CheckBox":
            view = new AppCompatCheckBox(context, attrs);
        case "RadioButton":
            view = new AppCompatRadioButton(context, attrs);
        case "CheckedTextView":
            view = new AppCompatCheckedTextView(context, attrs);
        case "AutoCompleteTextView":
            view = new AppCompatAutoCompleteTextView(context, attrs);
        case "MultiAutoCompleteTextView":
            view = new AppCompatMultiAutoCompleteTextView(context, attrs);
        case "RatingBar":
            view = new AppCompatRatingBar(context, attrs);
        case "SeekBar":
            view = new AppCompatSeekBar(context, attrs);

    if (view == null && originalContext != context) {
        // If the original context does not equal our themed context, then we need to manually
        // inflate it using the name so that android:theme takes effect.
        view = createViewFromTag(context, name, attrs);

    // ...

    return view;

Forcing the usage of non-AppCompat views

So, in order to force the creation of a regular ImageView (no AppCompatImageView) while still using AppCompatActivity you need to specify the complete class name, for example:


For more information on how layout inflation works you can see the amazing talk “LayoutInflater: Friend or Foe?” by Chris Jenx, author of Calligraphy.

In-depth article on Android touch events handling

Sometimes you need to intercept touches for a given ViewGroup, to temporarily change or disable it’s behavior. For example, recently I had to do this to temporarily intercept events for a RecyclerView during a tutorial, instructing the user how to select a particular item.

In this case, I could simply add an overlaid Layout on top of everything, but to keep as close to the real thing as possible, I decided to intercept the touch events at the RecyclerView level. The problem is, how to detect if the touch event needs to be intercepted or not. Two possible solutions came to my mind:

  • Intercept (or somehow disable) the events on each individual item, except on the one we’re interested on receiving it. For me it’s a bit messy to have this logic scattered.
  • Intercept the events on the RecyclerView, and check if the event belongs to the child, to allow further processing.

So, I decided to go for the second solution. To do that, I investigated a bit more in depth how touch events are handled in Android. This a really interesting article on how this happens: Understanding Android Input Touch Events System Framework (dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent, OnTouchListener.onTouch). Also, it is really useful to see the real code on how a ViewGroup handles this. Even if I could download Android source code from the Android Open Source Project web site, I didn’t really felt like it. So I found this copy online.

The interesting part for us is ViewGroup#dispatchTouchEvent(MotionEvent), and specifically in the loop where it iterates all the child and checks if the touch event is within the bounds of this child.

Based on this, I extracted the relevant code, which boils down to retrieving the hit rectangle of the child we want to allow processing the event, and checking it against the touch target. This class has to be overridden and the getAllowedChildView() method implemented, returning the child we’re interested in allowing the touch events go through.

public class CustomRecyclerView extends RecyclerView {

    private static final String LOG_MARKER = CustomRecyclerView.class.getName();

    private boolean scrollEnabled = true;

    public CustomRecyclerView(Context context) {

    public CustomRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);

    public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    public void setEnabledRecycleScroll(final boolean enable) {

        if (scrollEnabled == enable) {
            Logger.getInstance().debug(LOG_MARKER, "RecyclerView scrolling is already " +
                    (enable ? "enabled" : "disabled") + ", skipping");

        scrollEnabled = enable;

        if (!enable) {
            Logger.getInstance().debug(LOG_MARKER, "Disabling RecyclerView scrolling");
            addOnItemTouchListener(disablerListener); // disables scrolling
        } else {
            Logger.getInstance().debug(LOG_MARKER, "Enabling RecyclerView scrolling");
            removeOnItemTouchListener(disablerListener); // scrolling is enabled again


    // Override this method to return the child we're interested in allowing touch events
    // to go through
    abstract protected View getAllowedChildView();

    private RecyclerView.OnItemTouchListener disablerListener =
            new RecyclerView.OnItemTouchListener() {
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {

            // Inspired by ViewGroup#dispatchTouchEvent(MotionEvent). See
            // and
            final int action = ev.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                final float scrolledX = ev.getX() + CustomRecyclerView.this.getScrollX();
                final float scrolledY = ev.getY() + CustomRecyclerView.this.getScrollY();
                final Rect frame = new Rect();
                View allowedChildView = getAllowedChildView();
                if (allowedChildView != null) {
                    if (frame.contains((int) scrolledX, (int) scrolledY)) {
                        // Do not intercept the touch events for this child
                        return false;

            return true;

        public void onTouchEvent(RecyclerView rv, MotionEvent e) {