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