Merge "Add support for AppCompat widgets"
This commit is contained in:
@@ -584,6 +584,7 @@ public final class BridgeTypedArray extends TypedArray {
|
||||
if (value == null) {
|
||||
return defValue;
|
||||
}
|
||||
value = value.trim();
|
||||
|
||||
// if the value is just an integer, return it.
|
||||
try {
|
||||
@@ -595,6 +596,11 @@ public final class BridgeTypedArray extends TypedArray {
|
||||
// pass
|
||||
}
|
||||
|
||||
if (value.startsWith("#")) {
|
||||
// this looks like a color, do not try to parse it
|
||||
return defValue;
|
||||
}
|
||||
|
||||
// Handle the @id/<name>, @+id/<name> and @android:id/<name>
|
||||
// We need to return the exact value that was compiled (from the various R classes),
|
||||
// as these values can be reused internally with calls to findViewById().
|
||||
|
||||
@@ -42,9 +42,24 @@ import android.util.AttributeSet;
|
||||
import android.widget.NumberPicker;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
|
||||
import static com.android.SdkConstants.BUTTON;
|
||||
import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
|
||||
import static com.android.SdkConstants.CHECK_BOX;
|
||||
import static com.android.SdkConstants.EDIT_TEXT;
|
||||
import static com.android.SdkConstants.IMAGE_BUTTON;
|
||||
import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
|
||||
import static com.android.SdkConstants.RADIO_BUTTON;
|
||||
import static com.android.SdkConstants.SEEK_BAR;
|
||||
import static com.android.SdkConstants.SPINNER;
|
||||
import static com.android.SdkConstants.TEXT_VIEW;
|
||||
import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
|
||||
|
||||
/**
|
||||
@@ -53,6 +68,13 @@ import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
|
||||
public final class BridgeInflater extends LayoutInflater {
|
||||
|
||||
private final LayoutlibCallback mLayoutlibCallback;
|
||||
/**
|
||||
* If true, the inflater will try to replace the framework widgets with the AppCompat versions.
|
||||
* Ideally, this should be based on the activity being an AppCompat activity but since that is
|
||||
* not trivial to check from layoutlib, we currently base the decision on the current theme
|
||||
* being an AppCompat theme.
|
||||
*/
|
||||
private boolean mLoadAppCompatViews;
|
||||
private boolean mIsInMerge = false;
|
||||
private ResourceReference mResourceReference;
|
||||
private Map<View, String> mOpenDrawerLayouts;
|
||||
@@ -60,6 +82,15 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
// Keep in sync with the same value in LayoutInflater.
|
||||
private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
|
||||
|
||||
private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
|
||||
/** List of platform widgets that have an AppCompat version */
|
||||
private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
|
||||
new HashSet<>(
|
||||
Arrays.asList(TEXT_VIEW, "ImageSwitcher", BUTTON, EDIT_TEXT, SPINNER,
|
||||
IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
|
||||
AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
|
||||
SEEK_BAR)));
|
||||
|
||||
/**
|
||||
* List of class prefixes which are tried first by default.
|
||||
* <p/>
|
||||
@@ -75,13 +106,15 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
return sClassPrefixList;
|
||||
}
|
||||
|
||||
protected BridgeInflater(LayoutInflater original, Context newContext) {
|
||||
private BridgeInflater(LayoutInflater original, Context newContext) {
|
||||
super(original, newContext);
|
||||
newContext = getBaseContext(newContext);
|
||||
if (newContext instanceof BridgeContext) {
|
||||
mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
|
||||
mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
|
||||
} else {
|
||||
mLayoutlibCallback = null;
|
||||
mLoadAppCompatViews = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,10 +124,11 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
* @param context The Android application context.
|
||||
* @param layoutlibCallback the {@link LayoutlibCallback} object.
|
||||
*/
|
||||
public BridgeInflater(Context context, LayoutlibCallback layoutlibCallback) {
|
||||
public BridgeInflater(BridgeContext context, LayoutlibCallback layoutlibCallback) {
|
||||
super(context);
|
||||
mLayoutlibCallback = layoutlibCallback;
|
||||
mConstructorArgs[0] = context;
|
||||
mLoadAppCompatViews = context.isAppCompatTheme();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,28 +136,38 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
View view = null;
|
||||
|
||||
try {
|
||||
// First try to find a class using the default Android prefixes
|
||||
for (String prefix : sClassPrefixList) {
|
||||
if (mLoadAppCompatViews && APPCOMPAT_VIEWS.contains(name)) {
|
||||
// We are using an AppCompat theme so try to load the appcompat views
|
||||
view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs);
|
||||
|
||||
if (view == null) {
|
||||
mLoadAppCompatViews = false; // Do not try anymore
|
||||
}
|
||||
} else {
|
||||
|
||||
// First try to find a class using the default Android prefixes
|
||||
for (String prefix : sClassPrefixList) {
|
||||
try {
|
||||
view = createView(name, prefix, attrs);
|
||||
if (view != null) {
|
||||
break;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Ignore. We'll try again using the base class below.
|
||||
}
|
||||
}
|
||||
|
||||
// Next try using the parent loader. This will most likely only work for
|
||||
// fully-qualified class names.
|
||||
try {
|
||||
view = createView(name, prefix, attrs);
|
||||
if (view != null) {
|
||||
break;
|
||||
if (view == null) {
|
||||
view = super.onCreateView(name, attrs);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Ignore. We'll try again using the base class below.
|
||||
// Ignore. We'll try again using the custom view loader below.
|
||||
}
|
||||
}
|
||||
|
||||
// Next try using the parent loader. This will most likely only work for
|
||||
// fully-qualified class names.
|
||||
try {
|
||||
if (view == null) {
|
||||
view = super.onCreateView(name, attrs);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Ignore. We'll try again using the custom view loader below.
|
||||
}
|
||||
|
||||
// Finally try again using the custom view loader
|
||||
if (view == null) {
|
||||
view = loadCustomView(name, attrs);
|
||||
|
||||
@@ -110,6 +110,7 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // For use of Pair.
|
||||
public final class BridgeContext extends Context {
|
||||
private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
|
||||
|
||||
/** The map adds cookies to each view so that IDE can link xml tags to views. */
|
||||
private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
|
||||
@@ -153,6 +154,7 @@ public final class BridgeContext extends Context {
|
||||
private ClassLoader mClassLoader;
|
||||
private IBinder mBinder;
|
||||
private PackageManager mPackageManager;
|
||||
private Boolean mIsThemeAppCompat;
|
||||
|
||||
/**
|
||||
* Some applications that target both pre API 17 and post API 17, set the newer attrs to
|
||||
@@ -479,6 +481,36 @@ public final class BridgeContext extends Context {
|
||||
return Pair.of(null, Boolean.FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current selected theme is based on AppCompat
|
||||
*/
|
||||
public boolean isAppCompatTheme() {
|
||||
// If a cached value exists, return it.
|
||||
if (mIsThemeAppCompat != null) {
|
||||
return mIsThemeAppCompat;
|
||||
}
|
||||
// Ideally, we should check if the corresponding activity extends
|
||||
// android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
|
||||
StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme();
|
||||
// We can't simply check for parent using resources.themeIsParentOf() since the
|
||||
// inheritance structure isn't really what one would expect. The first common parent
|
||||
// between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
|
||||
boolean isThemeAppCompat = false;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (defaultTheme == null) {
|
||||
break;
|
||||
}
|
||||
// for loop ensures that we don't run into cyclic theme inheritance.
|
||||
if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
|
||||
isThemeAppCompat = true;
|
||||
break;
|
||||
}
|
||||
defaultTheme = mRenderResources.getParent(defaultTheme);
|
||||
}
|
||||
mIsThemeAppCompat = isThemeAppCompat;
|
||||
return isThemeAppCompat;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private ILayoutPullParser getParser(ResourceReference resource) {
|
||||
ILayoutPullParser parser;
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.android.ide.common.rendering.api.HardwareConfig;
|
||||
import com.android.ide.common.rendering.api.RenderResources;
|
||||
import com.android.ide.common.rendering.api.ResourceValue;
|
||||
import com.android.ide.common.rendering.api.SessionParams;
|
||||
import com.android.ide.common.rendering.api.StyleResourceValue;
|
||||
import com.android.layoutlib.bridge.Bridge;
|
||||
import com.android.layoutlib.bridge.android.BridgeContext;
|
||||
import com.android.layoutlib.bridge.android.RenderParamsFlags;
|
||||
@@ -94,7 +93,6 @@ class Layout extends RelativeLayout {
|
||||
private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
|
||||
private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
|
||||
private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
|
||||
private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
|
||||
|
||||
// Default sizes
|
||||
private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
|
||||
@@ -236,7 +234,7 @@ class Layout extends RelativeLayout {
|
||||
boolean isMenu = "menu".equals(params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
|
||||
|
||||
BridgeActionBar actionBar;
|
||||
if (mBuilder.isThemeAppCompat() && !isMenu) {
|
||||
if (context.isAppCompatTheme() && !isMenu) {
|
||||
actionBar = new AppCompatActionBar(context, params);
|
||||
} else {
|
||||
actionBar = new FrameworkActionBar(context, params);
|
||||
@@ -324,8 +322,6 @@ class Layout extends RelativeLayout {
|
||||
private boolean mTranslucentStatus;
|
||||
private boolean mTranslucentNav;
|
||||
|
||||
private Boolean mIsThemeAppCompat;
|
||||
|
||||
public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
|
||||
mParams = params;
|
||||
mContext = context;
|
||||
@@ -365,7 +361,7 @@ class Layout extends RelativeLayout {
|
||||
}
|
||||
// Check if an actionbar is needed
|
||||
boolean windowActionBar = getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
|
||||
!isThemeAppCompat(), true);
|
||||
!mContext.isAppCompatTheme(), true);
|
||||
if (windowActionBar) {
|
||||
mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
|
||||
} else {
|
||||
@@ -420,33 +416,6 @@ class Layout extends RelativeLayout {
|
||||
return mParams.getHardwareConfig().hasSoftwareButtons();
|
||||
}
|
||||
|
||||
private boolean isThemeAppCompat() {
|
||||
// If a cached value exists, return it.
|
||||
if (mIsThemeAppCompat != null) {
|
||||
return mIsThemeAppCompat;
|
||||
}
|
||||
// Ideally, we should check if the corresponding activity extends
|
||||
// android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
|
||||
StyleResourceValue defaultTheme = mResources.getDefaultTheme();
|
||||
// We can't simply check for parent using resources.themeIsParentOf() since the
|
||||
// inheritance structure isn't really what one would expect. The first common parent
|
||||
// between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
|
||||
boolean isThemeAppCompat = false;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
if (defaultTheme == null) {
|
||||
break;
|
||||
}
|
||||
// for loop ensures that we don't run into cyclic theme inheritance.
|
||||
if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
|
||||
isThemeAppCompat = true;
|
||||
break;
|
||||
}
|
||||
defaultTheme = mResources.getParent(defaultTheme);
|
||||
}
|
||||
mIsThemeAppCompat = isThemeAppCompat;
|
||||
return isThemeAppCompat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the status bar or nav bar are present, they are not translucent (i.e
|
||||
* content doesn't overlap with them).
|
||||
|
||||
@@ -77,6 +77,7 @@ public final class ResourceHelper {
|
||||
*/
|
||||
public static int getColor(String value) {
|
||||
if (value != null) {
|
||||
value = value.trim();
|
||||
if (!value.startsWith("#")) {
|
||||
if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
|
||||
throw new NumberFormatException(String.format(
|
||||
|
||||
Reference in New Issue
Block a user