Merge "Add RecyclerView support." into lmp-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b171be1a54
@@ -22,9 +22,13 @@ import com.android.ide.common.rendering.api.MergeCookie;
|
||||
import com.android.ide.common.rendering.api.ResourceReference;
|
||||
import com.android.ide.common.rendering.api.ResourceValue;
|
||||
import com.android.layoutlib.bridge.Bridge;
|
||||
import com.android.layoutlib.bridge.BridgeConstants;
|
||||
import com.android.layoutlib.bridge.android.BridgeContext;
|
||||
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
||||
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
|
||||
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil.LayoutManagerType;
|
||||
import com.android.layoutlib.bridge.impl.ParserFactory;
|
||||
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
|
||||
import com.android.resources.ResourceType;
|
||||
import com.android.util.Pair;
|
||||
|
||||
@@ -111,8 +115,7 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
} catch (Exception e) {
|
||||
// Wrap the real exception in a ClassNotFoundException, so that the calling method
|
||||
// can deal with it.
|
||||
ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
|
||||
throw exception;
|
||||
throw new ClassNotFoundException("onCreateView", e);
|
||||
}
|
||||
|
||||
setupViewInContext(view, attrs);
|
||||
@@ -123,7 +126,7 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
@Override
|
||||
public View createViewFromTag(View parent, String name, AttributeSet attrs,
|
||||
boolean inheritContext) {
|
||||
View view = null;
|
||||
View view;
|
||||
try {
|
||||
view = super.createViewFromTag(parent, name, attrs, inheritContext);
|
||||
} catch (InflateException e) {
|
||||
@@ -134,7 +137,7 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
// Wrap the real exception in an InflateException so that the calling
|
||||
// method can deal with it.
|
||||
InflateException exception = new InflateException();
|
||||
if (e2.getClass().equals(ClassNotFoundException.class) == false) {
|
||||
if (!e2.getClass().equals(ClassNotFoundException.class)) {
|
||||
exception.initCause(e2);
|
||||
} else {
|
||||
exception.initCause(e);
|
||||
@@ -184,7 +187,7 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
return inflate(bridgeParser, root);
|
||||
} catch (Exception e) {
|
||||
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
|
||||
"Failed to parse file " + f.getAbsolutePath(), e, null /*data*/);
|
||||
"Failed to parse file " + f.getAbsolutePath(), e, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -194,8 +197,7 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
return null;
|
||||
}
|
||||
|
||||
private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
|
||||
Exception{
|
||||
private View loadCustomView(String name, AttributeSet attrs) throws Exception {
|
||||
if (mProjectCallback != null) {
|
||||
// first get the classname in case it's not the node name
|
||||
if (name.equals("view")) {
|
||||
@@ -227,6 +229,20 @@ public final class BridgeInflater extends LayoutInflater {
|
||||
if (viewKey != null) {
|
||||
bc.addViewKey(view, viewKey);
|
||||
}
|
||||
if (RenderSessionImpl.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
|
||||
String type = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES,
|
||||
BridgeConstants.ATTR_LAYOUT_MANAGER_TYPE);
|
||||
if (type != null) {
|
||||
LayoutManagerType layoutManagerType = LayoutManagerType.getByDisplayName(type);
|
||||
if (layoutManagerType == null) {
|
||||
Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED,
|
||||
"LayoutManager (" + type + ") not found, falling back to " +
|
||||
"LinearLayoutManager", null);
|
||||
} else {
|
||||
bc.addCookie(view, layoutManagerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,4 +48,7 @@ public class BridgeConstants {
|
||||
public final static String MATCH_PARENT = "match_parent";
|
||||
public final static String FILL_PARENT = "fill_parent";
|
||||
public final static String WRAP_CONTENT = "wrap_content";
|
||||
|
||||
/** Attribute in the tools namespace used to specify layout manager for RecyclerView. */
|
||||
public static final String ATTR_LAYOUT_MANAGER_TYPE = "layoutManagerType";
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ public final class SessionParamsFlags {
|
||||
|
||||
public static final SessionParams.Key<String> FLAG_KEY_ROOT_TAG =
|
||||
new SessionParams.Key<String>("rootTag", String.class);
|
||||
public static final SessionParams.Key<Boolean> FLAG_KEY_RECYCLER_VIEW_SUPPORT =
|
||||
new SessionParams.Key<Boolean>("recyclerViewSupport", Boolean.class);
|
||||
|
||||
// Disallow instances.
|
||||
private SessionParamsFlags() {}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutlib.bridge.android.support;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
import com.android.ide.common.rendering.api.IProjectCallback;
|
||||
import com.android.ide.common.rendering.api.LayoutLog;
|
||||
import com.android.ide.common.rendering.api.SessionParams;
|
||||
import com.android.layoutlib.bridge.Bridge;
|
||||
import com.android.layoutlib.bridge.android.BridgeContext;
|
||||
import com.android.layoutlib.bridge.android.SessionParamsFlags;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static com.android.layoutlib.bridge.util.ReflectionUtils.*;
|
||||
|
||||
/**
|
||||
* Utility class for working with android.support.v7.widget.RecyclerView
|
||||
*/
|
||||
@SuppressWarnings("SpellCheckingInspection") // for "recycler".
|
||||
public class RecyclerViewUtil {
|
||||
|
||||
/**
|
||||
* Used by {@link LayoutManagerType}.
|
||||
* <p/>
|
||||
* Not declared inside the enum, since it needs to be accessible in the constructor.
|
||||
*/
|
||||
private static final Object CONTEXT = new Object();
|
||||
|
||||
public static final String CN_RECYCLER_VIEW = "android.support.v7.widget.RecyclerView";
|
||||
private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager";
|
||||
private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter";
|
||||
|
||||
/**
|
||||
* Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a
|
||||
* LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView}
|
||||
* that is passed.
|
||||
* <p/>
|
||||
* Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
|
||||
*/
|
||||
public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
|
||||
@NonNull SessionParams params) {
|
||||
try {
|
||||
setLayoutManager(recyclerView, context, params.getProjectCallback());
|
||||
Object adapter = createAdapter(params);
|
||||
setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
|
||||
} catch (ReflectionException e) {
|
||||
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
|
||||
"Error occured while trying to setup RecyclerView.", e, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
|
||||
@NonNull IProjectCallback callback) throws ReflectionException {
|
||||
Object cookie = context.getCookie(recyclerView);
|
||||
assert cookie == null || cookie instanceof LayoutManagerType;
|
||||
if (cookie == null) {
|
||||
cookie = LayoutManagerType.getDefault();
|
||||
}
|
||||
Object layoutManager = createLayoutManager((LayoutManagerType) cookie, context, callback);
|
||||
setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object createLayoutManager(@Nullable LayoutManagerType type,
|
||||
@NonNull Context context, @NonNull IProjectCallback callback)
|
||||
throws ReflectionException {
|
||||
if (type == null) {
|
||||
type = LayoutManagerType.getDefault();
|
||||
}
|
||||
try {
|
||||
return callback.loadView(type.getClassName(), type.getSignature(), type.getArgs(context));
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Object createAdapter(@NonNull SessionParams params) throws ReflectionException {
|
||||
Boolean ideSupport = params.getFlag(SessionParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
|
||||
if (ideSupport != Boolean.TRUE) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return params.getProjectCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]);
|
||||
} catch (Exception e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setProperty(@NonNull View recyclerView, @NonNull String propertyClassName,
|
||||
@Nullable Object propertyValue, @NonNull String propertySetter)
|
||||
throws ReflectionException {
|
||||
if (propertyValue != null) {
|
||||
Class<?> layoutManagerClass = getClassInstance(propertyValue, propertyClassName);
|
||||
Method setLayoutManager = getMethod(recyclerView.getClass(),
|
||||
propertySetter, layoutManagerClass);
|
||||
if (setLayoutManager != null) {
|
||||
invoke(setLayoutManager, recyclerView, propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the class hierarchy of {@code object} at runtime and returns the class matching
|
||||
* the name {@code className}.
|
||||
* <p/>
|
||||
* This is used when we cannot use Class.forName() since the class we want was loaded from a
|
||||
* different ClassLoader.
|
||||
*/
|
||||
@NonNull
|
||||
private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) {
|
||||
Class<?> superClass = object.getClass();
|
||||
while (superClass != null) {
|
||||
if (className.equals(superClass.getName())) {
|
||||
return superClass;
|
||||
}
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
throw new RuntimeException("invalid object/classname combination.");
|
||||
}
|
||||
|
||||
/** Supported LayoutManagers. */
|
||||
public enum LayoutManagerType {
|
||||
LINEAR_LAYOUT_MANGER("Linear",
|
||||
"android.support.v7.widget.LinearLayoutManager",
|
||||
new Class[]{Context.class}, new Object[]{CONTEXT}),
|
||||
GRID_LAYOUT_MANAGER("Grid",
|
||||
"android.support.v7.widget.GridLayoutManager",
|
||||
new Class[]{Context.class, int.class}, new Object[]{CONTEXT, 2}),
|
||||
STAGGERED_GRID_LAYOUT_MANAGER("StaggeredGrid",
|
||||
"android.support.v7.widget.StaggeredGridLayoutManager",
|
||||
new Class[]{int.class, int.class}, new Object[]{2, LinearLayout.VERTICAL});
|
||||
|
||||
private String mDisplayName;
|
||||
private String mClassName;
|
||||
private Class[] mSignature;
|
||||
private Object[] mArgs;
|
||||
|
||||
private static final HashMap<String, LayoutManagerType> sDisplayNameLookup =
|
||||
new HashMap<String, LayoutManagerType>();
|
||||
|
||||
static {
|
||||
for (LayoutManagerType type : LayoutManagerType.values()) {
|
||||
sDisplayNameLookup.put(type.mDisplayName, type);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutManagerType(String displayName, String className, Class[] signature, Object[] args) {
|
||||
mDisplayName = displayName;
|
||||
mClassName = className;
|
||||
mSignature = signature;
|
||||
mArgs = args;
|
||||
}
|
||||
|
||||
String getClassName() {
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
Class[] getSignature() {
|
||||
return mSignature;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Object[] getArgs(Context context) {
|
||||
Object[] args = new Object[mArgs.length];
|
||||
System.arraycopy(mArgs, 0, args, 0, mArgs.length);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == CONTEXT) {
|
||||
args[i] = context;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LayoutManagerType getDefault() {
|
||||
return LINEAR_LAYOUT_MANGER;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static LayoutManagerType getByDisplayName(@Nullable String className) {
|
||||
return sDisplayNameLookup.get(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.layoutlib.bridge.impl;
|
||||
|
||||
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
|
||||
|
||||
import com.android.ide.common.rendering.api.DrawableParams;
|
||||
import com.android.ide.common.rendering.api.HardwareConfig;
|
||||
import com.android.ide.common.rendering.api.ResourceValue;
|
||||
@@ -38,7 +36,6 @@ import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}.
|
||||
|
||||
@@ -23,6 +23,8 @@ 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.SUCCESS;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
import com.android.ide.common.rendering.api.AdapterBinding;
|
||||
import com.android.ide.common.rendering.api.HardwareConfig;
|
||||
import com.android.ide.common.rendering.api.IAnimationListener;
|
||||
@@ -51,6 +53,7 @@ import com.android.layoutlib.bridge.android.BridgeContext;
|
||||
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
|
||||
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
|
||||
import com.android.layoutlib.bridge.android.SessionParamsFlags;
|
||||
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
|
||||
import com.android.layoutlib.bridge.bars.BridgeActionBar;
|
||||
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
|
||||
import com.android.layoutlib.bridge.bars.Config;
|
||||
@@ -1327,6 +1330,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
|
||||
RecyclerViewUtil.setAdapter(view, getContext(), getParams());
|
||||
} else if (view instanceof ViewGroup) {
|
||||
ViewGroup group = (ViewGroup) view;
|
||||
final int count = group.getChildCount();
|
||||
@@ -1337,6 +1342,22 @@ 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.
|
||||
* @param tabHost the TabHost to setup.
|
||||
@@ -1494,6 +1515,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
||||
* @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
|
||||
* index 1 is with the offset.
|
||||
*/
|
||||
@NonNull
|
||||
private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
|
||||
ViewInfo[] result = new ViewInfo[2];
|
||||
if (view == null) {
|
||||
@@ -1589,6 +1611,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
||||
* The cookie for menu items are stored in menu item and not in the map from View stored in
|
||||
* BridgeContext.
|
||||
*/
|
||||
@Nullable
|
||||
private Object getViewKey(View view) {
|
||||
BridgeContext context = getContext();
|
||||
if (!(view instanceof MenuView.ItemView)) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutlib.bridge.util;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Utility to convert checked Reflection exceptions to unchecked exceptions.
|
||||
*/
|
||||
public class ReflectionUtils {
|
||||
|
||||
@Nullable
|
||||
public static Method getMethod(@NonNull Class<?> clazz, @NonNull String name,
|
||||
@Nullable Class<?>... params) throws ReflectionException {
|
||||
try {
|
||||
return clazz.getMethod(name, params);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Object invoke(@NonNull Method method, @Nullable Object object,
|
||||
@Nullable Object... args) throws ReflectionException {
|
||||
Exception ex;
|
||||
try {
|
||||
return method.invoke(object, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
ex = e;
|
||||
} catch (InvocationTargetException e) {
|
||||
ex = e;
|
||||
}
|
||||
throw new ReflectionException(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps all reflection related exceptions. Created since ReflectiveOperationException was
|
||||
* introduced in 1.7 and we are still on 1.6
|
||||
*/
|
||||
public static class ReflectionException extends Exception {
|
||||
public ReflectionException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ReflectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ReflectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ReflectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user