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 17 package com.android.layoutlib.bridge.android.support; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.ide.common.rendering.api.LayoutlibCallback; 21 import com.android.layoutlib.bridge.Bridge; 22 import com.android.layoutlib.bridge.android.BridgeContext; 23 import com.android.layoutlib.bridge.android.RenderParamsFlags; 24 import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.content.Context; 29 import android.view.View; 30 31 import static com.android.layoutlib.bridge.util.ReflectionUtils.getCause; 32 import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod; 33 import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke; 34 35 /** 36 * Utility class for working with android.support.v7.widget.RecyclerView 37 */ 38 public class RecyclerViewUtil { 39 40 private static final String RV_PKG_PREFIX = "android.support.v7.widget."; 41 public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView"; 42 private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager"; 43 private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter"; 44 45 // LinearLayoutManager related constants. 46 private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager"; 47 private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class}; 48 49 /** 50 * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a 51 * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView} 52 * that is passed. 53 * <p/> 54 * Any exceptions thrown during the process are logged in {@link Bridge#getLog()} 55 */ setAdapter(@onNull View recyclerView, @NonNull BridgeContext context, @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout)56 public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, 57 @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) { 58 try { 59 setLayoutManager(recyclerView, context, layoutlibCallback); 60 Object adapter = createAdapter(layoutlibCallback); 61 if (adapter != null) { 62 setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); 63 setProperty(adapter, int.class, adapterLayout, "setLayoutId"); 64 } 65 } catch (ReflectionException e) { 66 Throwable cause = getCause(e); 67 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 68 "Error occurred while trying to setup RecyclerView.", cause, null); 69 } 70 } 71 setLayoutManager(@onNull View recyclerView, @NonNull BridgeContext context, @NonNull LayoutlibCallback callback)72 private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, 73 @NonNull LayoutlibCallback callback) throws ReflectionException { 74 if (getLayoutManager(recyclerView) == null) { 75 // Only set the layout manager if not already set by the recycler view. 76 Object layoutManager = createLayoutManager(context, callback); 77 if (layoutManager != null) { 78 setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); 79 } 80 } 81 } 82 83 /** Creates a LinearLayoutManager using the provided context. */ 84 @Nullable createLayoutManager(@onNull Context context, @NonNull LayoutlibCallback callback)85 private static Object createLayoutManager(@NonNull Context context, 86 @NonNull LayoutlibCallback callback) 87 throws ReflectionException { 88 try { 89 return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE, 90 new Object[]{context}); 91 } catch (Exception e) { 92 throw new ReflectionException(e); 93 } 94 } 95 96 @Nullable getLayoutManager(View recyclerView)97 private static Object getLayoutManager(View recyclerView) throws ReflectionException { 98 return invoke(getMethod(recyclerView.getClass(), "getLayoutManager"), recyclerView); 99 } 100 101 @Nullable createAdapter(@onNull LayoutlibCallback layoutlibCallback)102 private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback) 103 throws ReflectionException { 104 Boolean ideSupport = 105 layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT); 106 if (ideSupport != Boolean.TRUE) { 107 return null; 108 } 109 try { 110 return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]); 111 } catch (Exception e) { 112 throw new ReflectionException(e); 113 } 114 } 115 setProperty(@onNull Object object, @NonNull String propertyClassName, @NonNull Object propertyValue, @NonNull String propertySetter)116 private static void setProperty(@NonNull Object object, @NonNull String propertyClassName, 117 @NonNull Object propertyValue, @NonNull String propertySetter) 118 throws ReflectionException { 119 Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName); 120 setProperty(object, propertyClass, propertyValue, propertySetter); 121 } 122 setProperty(@onNull Object object, @NonNull Class<?> propertyClass, @Nullable Object propertyValue, @NonNull String propertySetter)123 private static void setProperty(@NonNull Object object, @NonNull Class<?> propertyClass, 124 @Nullable Object propertyValue, @NonNull String propertySetter) 125 throws ReflectionException { 126 invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue); 127 } 128 129 /** 130 * Looks through the class hierarchy of {@code object} at runtime and returns the class matching 131 * the name {@code className}. 132 * <p/> 133 * This is used when we cannot use Class.forName() since the class we want was loaded from a 134 * different ClassLoader. 135 */ 136 @NonNull getClassInstance(@onNull Object object, @NonNull String className)137 private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { 138 Class<?> superClass = object.getClass(); 139 while (superClass != null) { 140 if (className.equals(superClass.getName())) { 141 return superClass; 142 } 143 superClass = superClass.getSuperclass(); 144 } 145 throw new RuntimeException("invalid object/classname combination."); 146 } 147 } 148