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 anAppCompatImageView
instead of anImageView
. The same happened with aTextView
(withAppCompatTextView
).
So, why Android View
s 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
andbackgroundTintMode
.- Allows dynamic tint of its image via the image tint methods in
ImageViewCompat
.- Allows setting of the image tint using
tint
andtintMode
.
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 AppCompatViewInflater.java (replaced with Android Code Search, now AndroidX version, code may be slightly different).
The function responsible for creating the Views
is AppCompatViewInflater#
, 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);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}
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:
<android.widget.ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/test"/>
For more information on how layout inflation works you can see the amazing talk “LayoutInflater: Friend or Foe?” by Chris Jenx, author of Calligraphy.