RecyclerViewUtil.java revision 37dbb8b7f3c069196040eed3a03006647db7fa5b
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.annotations.NonNull; 20import com.android.annotations.Nullable; 21import com.android.ide.common.rendering.api.LayoutLog; 22import com.android.ide.common.rendering.api.LayoutlibCallback; 23import com.android.ide.common.rendering.api.SessionParams; 24import com.android.layoutlib.bridge.Bridge; 25import com.android.layoutlib.bridge.android.BridgeContext; 26import com.android.layoutlib.bridge.android.SessionParamsFlags; 27 28import android.content.Context; 29import android.view.View; 30import android.widget.LinearLayout; 31 32import java.lang.reflect.Method; 33 34import static com.android.layoutlib.bridge.util.ReflectionUtils.*; 35 36/** 37 * Utility class for working with android.support.v7.widget.RecyclerView 38 */ 39@SuppressWarnings("SpellCheckingInspection") // for "recycler". 40public class RecyclerViewUtil { 41 42 /** 43 * Used by {@link LayoutManagerType}. 44 * <p/> 45 * Not declared inside the enum, since it needs to be accessible in the constructor. 46 */ 47 private static final Object CONTEXT = new Object(); 48 49 public static final String CN_RECYCLER_VIEW = "android.support.v7.widget.RecyclerView"; 50 private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager"; 51 private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter"; 52 53 /** 54 * Tries to create an Adapter ({@code android.support.v7.widget.RecyclerView.Adapter} and a 55 * LayoutManager {@code RecyclerView.LayoutManager} and assign these to the {@code RecyclerView} 56 * that is passed. 57 * <p/> 58 * Any exceptions thrown during the process are logged in {@link Bridge#getLog()} 59 */ 60 public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, 61 @NonNull SessionParams params) { 62 try { 63 setLayoutManager(recyclerView, context, params.getLayoutlibCallback()); 64 Object adapter = createAdapter(params); 65 setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); 66 } catch (ReflectionException e) { 67 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 68 "Error occured while trying to setup RecyclerView.", e, null); 69 } 70 } 71 72 private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context, 73 @NonNull LayoutlibCallback callback) throws ReflectionException { 74 Object cookie = context.getCookie(recyclerView); 75 assert cookie == null || cookie instanceof LayoutManagerType || cookie instanceof String; 76 if (!(cookie instanceof LayoutManagerType)) { 77 if (cookie != null) { 78 // TODO: When layoutlib API is updated, try to load the class with a null 79 // constructor or a constructor taking one argument - the context. 80 Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, 81 "LayoutManager (" + cookie + ") not found, falling back to " + 82 "LinearLayoutManager", null); 83 } 84 cookie = LayoutManagerType.getDefault(); 85 } 86 Object layoutManager = createLayoutManager((LayoutManagerType) cookie, context, callback); 87 setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager"); 88 } 89 90 @Nullable 91 private static Object createLayoutManager(@Nullable LayoutManagerType type, 92 @NonNull Context context, @NonNull LayoutlibCallback callback) 93 throws ReflectionException { 94 if (type == null) { 95 type = LayoutManagerType.getDefault(); 96 } 97 try { 98 return callback.loadView(type.getClassName(), type.getSignature(), type.getArgs(context)); 99 } catch (Exception e) { 100 throw new ReflectionException(e); 101 } 102 } 103 104 @Nullable 105 private static Object createAdapter(@NonNull SessionParams params) throws ReflectionException { 106 Boolean ideSupport = params.getFlag(SessionParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT); 107 if (ideSupport != Boolean.TRUE) { 108 return null; 109 } 110 try { 111 return params.getLayoutlibCallback().loadView(CN_ADAPTER, new Class[0], new Object[0]); 112 } catch (Exception e) { 113 throw new ReflectionException(e); 114 } 115 } 116 117 private static void setProperty(@NonNull View recyclerView, @NonNull String propertyClassName, 118 @Nullable Object propertyValue, @NonNull String propertySetter) 119 throws ReflectionException { 120 if (propertyValue != null) { 121 Class<?> layoutManagerClass = getClassInstance(propertyValue, propertyClassName); 122 Method setLayoutManager = getMethod(recyclerView.getClass(), 123 propertySetter, layoutManagerClass); 124 if (setLayoutManager != null) { 125 invoke(setLayoutManager, recyclerView, propertyValue); 126 } 127 } 128 } 129 130 /** 131 * Looks through the class hierarchy of {@code object} at runtime and returns the class matching 132 * the name {@code className}. 133 * <p/> 134 * This is used when we cannot use Class.forName() since the class we want was loaded from a 135 * different ClassLoader. 136 */ 137 @NonNull 138 private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { 139 Class<?> superClass = object.getClass(); 140 while (superClass != null) { 141 if (className.equals(superClass.getName())) { 142 return superClass; 143 } 144 superClass = superClass.getSuperclass(); 145 } 146 throw new RuntimeException("invalid object/classname combination."); 147 } 148 149 /** Supported LayoutManagers. */ 150 public enum LayoutManagerType { 151 LINEAR_LAYOUT_MANGER("Linear", 152 "android.support.v7.widget.LinearLayoutManager", 153 new Class[]{Context.class}, new Object[]{CONTEXT}), 154 GRID_LAYOUT_MANAGER("Grid", 155 "android.support.v7.widget.GridLayoutManager", 156 new Class[]{Context.class, int.class}, new Object[]{CONTEXT, 2}), 157 STAGGERED_GRID_LAYOUT_MANAGER("StaggeredGrid", 158 "android.support.v7.widget.StaggeredGridLayoutManager", 159 new Class[]{int.class, int.class}, new Object[]{2, LinearLayout.VERTICAL}); 160 161 private String mLogicalName; 162 private String mClassName; 163 private Class[] mSignature; 164 private Object[] mArgs; 165 166 LayoutManagerType(String logicalName, String className, Class[] signature, Object[] args) { 167 mLogicalName = logicalName; 168 mClassName = className; 169 mSignature = signature; 170 mArgs = args; 171 } 172 173 String getClassName() { 174 return mClassName; 175 } 176 177 Class[] getSignature() { 178 return mSignature; 179 } 180 181 @NonNull 182 Object[] getArgs(Context context) { 183 Object[] args = new Object[mArgs.length]; 184 System.arraycopy(mArgs, 0, args, 0, mArgs.length); 185 for (int i = 0; i < args.length; i++) { 186 if (args[i] == CONTEXT) { 187 args[i] = context; 188 } 189 } 190 return args; 191 } 192 193 @NonNull 194 public static LayoutManagerType getDefault() { 195 return LINEAR_LAYOUT_MANGER; 196 } 197 198 @Nullable 199 public static LayoutManagerType getByLogicalName(@NonNull String logicalName) { 200 for (LayoutManagerType type : values()) { 201 if (logicalName.equals(type.mLogicalName)) { 202 return type; 203 } 204 } 205 return null; 206 } 207 208 @Nullable 209 public static LayoutManagerType getByClassName(@NonNull String className) { 210 for (LayoutManagerType type : values()) { 211 if (className.equals(type.mClassName)) { 212 return type; 213 } 214 } 215 return null; 216 } 217 } 218} 219