Implement tools:list_item for RecyclerView. [DO NOT MERGE]
It's now possible to use tools:list_item attribute for RecyclerView to
point to a default layout, rather than always using a TextView.
Change-Id: I5d522b2f0ca38b420fddfcb0f73a26d95707da79
(cherry picked from commit 61f23e9bf7)
This commit is contained in:
@@ -25,7 +25,9 @@ import com.android.layoutlib.bridge.Bridge;
|
|||||||
import com.android.layoutlib.bridge.BridgeConstants;
|
import com.android.layoutlib.bridge.BridgeConstants;
|
||||||
import com.android.layoutlib.bridge.android.BridgeContext;
|
import com.android.layoutlib.bridge.android.BridgeContext;
|
||||||
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
||||||
|
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
|
||||||
import com.android.layoutlib.bridge.impl.ParserFactory;
|
import com.android.layoutlib.bridge.impl.ParserFactory;
|
||||||
|
import com.android.layoutlib.bridge.util.ReflectionUtils;
|
||||||
import com.android.resources.ResourceType;
|
import com.android.resources.ResourceType;
|
||||||
import com.android.util.Pair;
|
import com.android.util.Pair;
|
||||||
|
|
||||||
@@ -112,15 +114,9 @@ public final class BridgeInflater extends LayoutInflater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally try again using the custom view loader
|
// Finally try again using the custom view loader
|
||||||
try {
|
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
view = loadCustomView(name, attrs);
|
view = loadCustomView(name, attrs);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
// If the class was not found, we throw the exception directly, because this
|
|
||||||
// method is already expected to throw it.
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Wrap the real exception in a ClassNotFoundException, so that the calling method
|
// Wrap the real exception in a ClassNotFoundException, so that the calling method
|
||||||
// can deal with it.
|
// can deal with it.
|
||||||
@@ -242,6 +238,25 @@ public final class BridgeInflater extends LayoutInflater {
|
|||||||
bc.setScrollYPos(view, value);
|
bc.setScrollYPos(view, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
|
||||||
|
Integer resourceId = null;
|
||||||
|
String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
|
||||||
|
BridgeConstants.ATTR_LIST_ITEM);
|
||||||
|
if (attrVal != null && !attrVal.isEmpty()) {
|
||||||
|
ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false);
|
||||||
|
if (resValue.isFramework()) {
|
||||||
|
resourceId = Bridge.getResourceId(resValue.getResourceType(),
|
||||||
|
resValue.getName());
|
||||||
|
} else {
|
||||||
|
resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
|
||||||
|
resValue.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resourceId == null) {
|
||||||
|
resourceId = 0;
|
||||||
|
}
|
||||||
|
RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class BridgeConstants {
|
|||||||
|
|
||||||
/** App auto namespace */
|
/** App auto namespace */
|
||||||
public final static String NS_APP_RES_AUTO = "http://schemas.android.com/apk/res-auto";
|
public final static String NS_APP_RES_AUTO = "http://schemas.android.com/apk/res-auto";
|
||||||
|
public final static String NS_TOOLS_URI = "http://schemas.android.com/tools";
|
||||||
|
|
||||||
public final static String R = "com.android.internal.R";
|
public final static String R = "com.android.internal.R";
|
||||||
|
|
||||||
@@ -50,5 +51,5 @@ public class BridgeConstants {
|
|||||||
public final static String WRAP_CONTENT = "wrap_content";
|
public final static String WRAP_CONTENT = "wrap_content";
|
||||||
|
|
||||||
/** Attribute in the tools namespace used to specify layout manager for RecyclerView. */
|
/** Attribute in the tools namespace used to specify layout manager for RecyclerView. */
|
||||||
public static final String ATTR_LAYOUT_MANAGER_TYPE = "layoutManager";
|
public static final String ATTR_LIST_ITEM = "list_item";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package com.android.layoutlib.bridge.android.support;
|
|||||||
|
|
||||||
import com.android.ide.common.rendering.api.LayoutLog;
|
import com.android.ide.common.rendering.api.LayoutLog;
|
||||||
import com.android.ide.common.rendering.api.LayoutlibCallback;
|
import com.android.ide.common.rendering.api.LayoutlibCallback;
|
||||||
import com.android.ide.common.rendering.api.SessionParams;
|
|
||||||
import com.android.layoutlib.bridge.Bridge;
|
import com.android.layoutlib.bridge.Bridge;
|
||||||
import com.android.layoutlib.bridge.android.BridgeContext;
|
import com.android.layoutlib.bridge.android.BridgeContext;
|
||||||
import com.android.layoutlib.bridge.android.RenderParamsFlags;
|
import com.android.layoutlib.bridge.android.RenderParamsFlags;
|
||||||
@@ -37,7 +36,6 @@ import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
|
|||||||
/**
|
/**
|
||||||
* Utility class for working with android.support.v7.widget.RecyclerView
|
* Utility class for working with android.support.v7.widget.RecyclerView
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("SpellCheckingInspection") // for "recycler".
|
|
||||||
public class RecyclerViewUtil {
|
public class RecyclerViewUtil {
|
||||||
|
|
||||||
private static final String RV_PKG_PREFIX = "android.support.v7.widget.";
|
private static final String RV_PKG_PREFIX = "android.support.v7.widget.";
|
||||||
@@ -57,15 +55,24 @@ public class RecyclerViewUtil {
|
|||||||
* Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
|
* Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
|
||||||
*/
|
*/
|
||||||
public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
|
public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
|
||||||
@NonNull SessionParams params) {
|
@NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) {
|
||||||
try {
|
try {
|
||||||
setLayoutManager(recyclerView, context, params.getLayoutlibCallback());
|
setLayoutManager(recyclerView, context, layoutlibCallback);
|
||||||
Object adapter = createAdapter(params);
|
Object adapter = createAdapter(layoutlibCallback);
|
||||||
|
if (adapter != null) {
|
||||||
setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
|
setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
|
||||||
} catch (ReflectionException e) {
|
setProperty(adapter, int.class, adapterLayout, "setLayoutId");
|
||||||
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
|
|
||||||
"Error occured while trying to setup RecyclerView.", e, null);
|
|
||||||
}
|
}
|
||||||
|
} catch (ReflectionException e) {
|
||||||
|
Throwable cause = getCause(e);
|
||||||
|
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
|
||||||
|
"Error occurred while trying to setup RecyclerView.", cause, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Throwable getCause(Throwable throwable) {
|
||||||
|
Throwable cause = throwable.getCause();
|
||||||
|
return cause == null ? throwable : cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
|
private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
|
||||||
@@ -73,9 +80,11 @@ public class RecyclerViewUtil {
|
|||||||
if (getLayoutManager(recyclerView) == null) {
|
if (getLayoutManager(recyclerView) == null) {
|
||||||
// Only set the layout manager if not already set by the recycler view.
|
// Only set the layout manager if not already set by the recycler view.
|
||||||
Object layoutManager = createLayoutManager(context, callback);
|
Object layoutManager = createLayoutManager(context, callback);
|
||||||
|
if (layoutManager != null) {
|
||||||
setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
|
setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates a LinearLayoutManager using the provided context. */
|
/** Creates a LinearLayoutManager using the provided context. */
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -84,41 +93,46 @@ public class RecyclerViewUtil {
|
|||||||
throws ReflectionException {
|
throws ReflectionException {
|
||||||
try {
|
try {
|
||||||
return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE,
|
return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE,
|
||||||
new Object[]{ context});
|
new Object[]{context});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ReflectionException(e);
|
throw new ReflectionException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Object getLayoutManager(View recyclerview) throws ReflectionException {
|
private static Object getLayoutManager(View recyclerView) throws ReflectionException {
|
||||||
Method getLayoutManager = getMethod(recyclerview.getClass(), "getLayoutManager");
|
Method getLayoutManager = getMethod(recyclerView.getClass(), "getLayoutManager");
|
||||||
return getLayoutManager != null ? invoke(getLayoutManager, recyclerview) : null;
|
return getLayoutManager != null ? invoke(getLayoutManager, recyclerView) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Object createAdapter(@NonNull SessionParams params) throws ReflectionException {
|
private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback)
|
||||||
Boolean ideSupport = params.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
|
throws ReflectionException {
|
||||||
|
Boolean ideSupport =
|
||||||
|
layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
|
||||||
if (ideSupport != Boolean.TRUE) {
|
if (ideSupport != Boolean.TRUE) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return params.getLayoutlibCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]);
|
return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ReflectionException(e);
|
throw new ReflectionException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setProperty(@NonNull View recyclerView, @NonNull String propertyClassName,
|
private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
|
||||||
|
@NonNull Object propertyValue, @NonNull String propertySetter)
|
||||||
|
throws ReflectionException {
|
||||||
|
Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName);
|
||||||
|
setProperty(object, propertyClass, propertyValue, propertySetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass,
|
||||||
@Nullable Object propertyValue, @NonNull String propertySetter)
|
@Nullable Object propertyValue, @NonNull String propertySetter)
|
||||||
throws ReflectionException {
|
throws ReflectionException {
|
||||||
if (propertyValue != null) {
|
Method setter = getMethod(object.getClass(), propertySetter, propertyClass);
|
||||||
Class<?> layoutManagerClass = getClassInstance(propertyValue, propertyClassName);
|
if (setter != null) {
|
||||||
Method setLayoutManager = getMethod(recyclerView.getClass(),
|
invoke(setter, object, propertyValue);
|
||||||
propertySetter, layoutManagerClass);
|
|
||||||
if (setLayoutManager != null) {
|
|
||||||
invoke(setLayoutManager, recyclerView, propertyValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
|
|||||||
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
||||||
import com.android.layoutlib.bridge.android.RenderParamsFlags;
|
import com.android.layoutlib.bridge.android.RenderParamsFlags;
|
||||||
import com.android.layoutlib.bridge.android.support.DesignLibUtil;
|
import com.android.layoutlib.bridge.android.support.DesignLibUtil;
|
||||||
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
|
|
||||||
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
|
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
|
||||||
import com.android.layoutlib.bridge.bars.BridgeActionBar;
|
import com.android.layoutlib.bridge.bars.BridgeActionBar;
|
||||||
import com.android.layoutlib.bridge.bars.Config;
|
import com.android.layoutlib.bridge.bars.Config;
|
||||||
@@ -116,6 +115,7 @@ import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLA
|
|||||||
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
|
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
|
||||||
import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
|
import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
|
||||||
import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
|
import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
|
||||||
|
import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class implementing the render session.
|
* Class implementing the render session.
|
||||||
@@ -1351,8 +1351,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
|
|
||||||
RecyclerViewUtil.setAdapter(view, getContext(), getParams());
|
|
||||||
} else if (view instanceof ViewGroup) {
|
} else if (view instanceof ViewGroup) {
|
||||||
ViewGroup group = (ViewGroup) view;
|
ViewGroup group = (ViewGroup) view;
|
||||||
final int count = group.getChildCount();
|
final int count = group.getChildCount();
|
||||||
@@ -1456,22 +1454,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the object is an instance of a class named {@code className}. This doesn't work
|
|
||||||
* for interfaces.
|
|
||||||
*/
|
|
||||||
public static boolean isInstanceOf(Object object, String className) {
|
|
||||||
Class superClass = object.getClass();
|
|
||||||
while (superClass != null) {
|
|
||||||
String name = superClass.getName();
|
|
||||||
if (name.equals(className)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
superClass = superClass.getSuperclass();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a {@link TabHost} object.
|
* Sets up a {@link TabHost} object.
|
||||||
* @param tabHost the TabHost to setup.
|
* @param tabHost the TabHost to setup.
|
||||||
|
|||||||
@@ -51,6 +51,22 @@ public class ReflectionUtils {
|
|||||||
throw new ReflectionException(ex);
|
throw new ReflectionException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the object is an instance of a class named {@code className}. This doesn't work
|
||||||
|
* for interfaces.
|
||||||
|
*/
|
||||||
|
public static boolean isInstanceOf(Object object, String className) {
|
||||||
|
Class superClass = object.getClass();
|
||||||
|
while (superClass != null) {
|
||||||
|
String name = superClass.getName();
|
||||||
|
if (name.equals(className)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
superClass = superClass.getSuperclass();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps all reflection related exceptions. Created since ReflectiveOperationException was
|
* Wraps all reflection related exceptions. Created since ReflectiveOperationException was
|
||||||
* introduced in 1.7 and we are still on 1.6
|
* introduced in 1.7 and we are still on 1.6
|
||||||
|
|||||||
Reference in New Issue
Block a user