blob: 2feab7a9c0aa116a8be1ad163a29cc35df2a7c0f [file] [log] [blame]
/*
* 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);
}
}
}