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