RecyclerViewUtil.java revision ccbc11770397888cf7780925bb4c7cf1d2f2f80e
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.layoutlib.bridge.android.support; 18 19import com.android.ide.common.rendering.api.LayoutLog; 20import com.android.ide.common.rendering.api.LayoutlibCallback; 21import com.android.layoutlib.bridge.Bridge; 22import com.android.layoutlib.bridge.android.BridgeContext; 23import com.android.layoutlib.bridge.android.RenderParamsFlags; 24import com.android.layoutlib.bridge.util.ReflectionUtils; 25 26import android.annotation.NonNull; 27import android.annotation.Nullable; 28import android.content.Context; 29import android.view.View; 30 31import java.lang.reflect.Method; 32 33import static com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; 34import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause; 35import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod; 36import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke; 37 38/** 39 * Utility class for working with android.support.v7.widget.RecyclerView 40 */ 41public class RecyclerViewUtil { 42 43 private static final String RV_PKG_PREFIX = "android.support.v7.widget."; 44 public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView"; 45 private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager"; 46 private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter"; 47 48 // LinearLayoutManager related constants. 49 private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager"; 50 private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class}; 51 52 /** 53 * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a 54 * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView} 55 * that is passed. 56 * <p/> 57 * Any exceptions thrown during the process are logged in {@link Bridge#getLog()} 58 */ 59 public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, 60 @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) { 61 try { 62 setLayoutManager(recyclerView, context, layoutlibCallback); 63 Object adapter = createAdapter(layoutlibCallback); 64 if (adapter != null) { 65 setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); 66 setProperty(adapter, int.class, adapterLayout, "setLayoutId"); 67 } 68 } catch (ReflectionException e) { 69 Throwable cause = getCause(e); 70 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 71 "Error occurred while trying to setup RecyclerView.", cause, null); 72 } 73 } 74 75 private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, 76 @NonNull LayoutlibCallback callback) throws ReflectionException { 77 if (getLayoutManager(recyclerView) == null) { 78 // Only set the layout manager if not already set by the recycler view. 79 Object layoutManager = createLayoutManager(context, callback); 80 if (layoutManager != null) { 81 setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); 82 } 83 } 84 } 85 86 /** Creates a LinearLayoutManager using the provided context. */ 87 @Nullable 88 private static Object createLayoutManager(@NonNull Context context, 89 @NonNull LayoutlibCallback callback) 90 throws ReflectionException { 91 try { 92 return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE, 93 new Object[]{context}); 94 } catch (Exception e) { 95 throw new ReflectionException(e); 96 } 97 } 98 99 @Nullable 100 private static Object getLayoutManager(View recyclerView) throws ReflectionException { 101 Method getLayoutManager = getMethod(recyclerView.getClass(), "getLayoutManager"); 102 return getLayoutManager != null ? invoke(getLayoutManager, recyclerView) : null; 103 } 104 105 @Nullable 106 private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback) 107 throws ReflectionException { 108 Boolean ideSupport = 109 layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT); 110 if (ideSupport != Boolean.TRUE) { 111 return null; 112 } 113 try { 114 return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]); 115 } catch (Exception e) { 116 throw new ReflectionException(e); 117 } 118 } 119 120 private static void setProperty(@NonNull Object object, @NonNull String propertyClassName, 121 @NonNull Object propertyValue, @NonNull String propertySetter) 122 throws ReflectionException { 123 Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName); 124 setProperty(object, propertyClass, propertyValue, propertySetter); 125 } 126 127 private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass, 128 @Nullable Object propertyValue, @NonNull String propertySetter) 129 throws ReflectionException { 130 Method setter = getMethod(object.getClass(), propertySetter, propertyClass); 131 if (setter != null) { 132 invoke(setter, object, propertyValue); 133 } 134 } 135 136 /** 137 * Looks through the class hierarchy of {@code object} at runtime and returns the class matching 138 * the name {@code className}. 139 * <p/> 140 * This is used when we cannot use Class.forName() since the class we want was loaded from a 141 * different ClassLoader. 142 */ 143 @NonNull 144 private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { 145 Class<?> superClass = object.getClass(); 146 while (superClass != null) { 147 if (className.equals(superClass.getName())) { 148 return superClass; 149 } 150 superClass = superClass.getSuperclass(); 151 } 152 throw new RuntimeException("invalid object/classname combination."); 153 } 154} 155