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