summaryrefslogtreecommitdiff
path: root/library/recyclerview/src/com/android/setupwizardlib/template
diff options
context:
space:
mode:
authorMaurice Lam <yukl@google.com>2017-03-28 12:48:40 -0700
committerMaurice Lam <yukl@google.com>2017-03-28 14:23:21 -0700
commit83862bb59558fc044de9aa0d6e9407be53af8b81 (patch)
tree032855cc188420699c048478a6a24c44731a3151 /library/recyclerview/src/com/android/setupwizardlib/template
parent9955331ed7bda114488b1a4701456ec478ff63bf (diff)
downloadsetupwizard-83862bb59558fc044de9aa0d6e9407be53af8b81.tar.gz
Rename SuwLib directories
Rename eclair-mr1 to gingerbread to reflect the min SDK version change, and full-support to recyclerview to better reflect what's inside the directory Also added comments and applied style fixes to keep checkstyle happy. Test: Existing tests pass Change-Id: I20332f718f2aae04092d5e45de944b1efce1a596
Diffstat (limited to 'library/recyclerview/src/com/android/setupwizardlib/template')
-rw-r--r--library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java235
-rw-r--r--library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java83
2 files changed, 318 insertions, 0 deletions
diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java
new file mode 100644
index 0000000..56751d4
--- /dev/null
+++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerMixin.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.template;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.setupwizardlib.DividerItemDecoration;
+import com.android.setupwizardlib.R;
+import com.android.setupwizardlib.TemplateLayout;
+import com.android.setupwizardlib.items.ItemHierarchy;
+import com.android.setupwizardlib.items.ItemInflater;
+import com.android.setupwizardlib.items.RecyclerItemAdapter;
+import com.android.setupwizardlib.util.DrawableLayoutDirectionHelper;
+import com.android.setupwizardlib.view.HeaderRecyclerView;
+import com.android.setupwizardlib.view.HeaderRecyclerView.HeaderAdapter;
+
+/**
+ * A {@link Mixin} for interacting with templates with recycler views. This mixin constructor takes
+ * the instance of the recycler view to allow it to be instantiated dynamically, as in the case for
+ * preference fragments.
+ *
+ * <p>Unlike typical mixins, this mixin is designed to be created in onTemplateInflated, which is
+ * called by the super constructor, and then parse the XML attributes later in the constructor.
+ */
+public class RecyclerMixin implements Mixin {
+
+ private TemplateLayout mTemplateLayout;
+
+ @NonNull
+ private final RecyclerView mRecyclerView;
+
+ @Nullable
+ private View mHeader;
+
+ @NonNull
+ private DividerItemDecoration mDividerDecoration;
+
+ private Drawable mDefaultDivider;
+ private Drawable mDivider;
+ private int mDividerInset;
+
+ /**
+ * Creates the RecyclerMixin. Unlike typical mixins which are created in the constructor, this
+ * mixin should be called in {@link TemplateLayout#onTemplateInflated()}, which is called by
+ * the super constructor, because the recycler view and the header needs to be made available
+ * before other mixins from the super class.
+ *
+ * @param layout The layout this mixin belongs to.
+ */
+ public RecyclerMixin(@NonNull TemplateLayout layout, @NonNull RecyclerView recyclerView) {
+ mTemplateLayout = layout;
+
+ mDividerDecoration = new DividerItemDecoration(mTemplateLayout.getContext());
+
+ // The recycler view needs to be available
+ mRecyclerView = recyclerView;
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(mTemplateLayout.getContext()));
+
+ if (recyclerView instanceof HeaderRecyclerView) {
+ mHeader = ((HeaderRecyclerView) recyclerView).getHeader();
+ }
+
+ mRecyclerView.addItemDecoration(mDividerDecoration);
+ }
+
+ /**
+ * Parse XML attributes and configures this mixin and the recycler view accordingly. This should
+ * be called from the constructor of the layout.
+ *
+ * @param attrs The {@link AttributeSet} as passed into the constructor. Can be null if the
+ * layout was not created from XML.
+ * @param defStyleAttr The default style attribute as passed into the layout constructor. Can be
+ * 0 if it is not needed.
+ */
+ public void parseAttributes(@Nullable AttributeSet attrs, int defStyleAttr) {
+ final Context context = mTemplateLayout.getContext();
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.SuwRecyclerMixin, defStyleAttr, 0);
+
+ final int entries = a.getResourceId(R.styleable.SuwRecyclerMixin_android_entries, 0);
+ if (entries != 0) {
+ final ItemHierarchy inflated = new ItemInflater(context).inflate(entries);
+ final RecyclerItemAdapter adapter = new RecyclerItemAdapter(inflated);
+ adapter.setHasStableIds(a.getBoolean(
+ R.styleable.SuwRecyclerMixin_suwHasStableIds, false));
+ setAdapter(adapter);
+ }
+ int dividerInset =
+ a.getDimensionPixelSize(R.styleable.SuwRecyclerMixin_suwDividerInset, 0);
+ setDividerInset(dividerInset);
+ a.recycle();
+ }
+
+ /**
+ * @return The recycler view contained in the layout, as marked by
+ * {@code @id/suw_recycler_view}. This will return {@code null} if the recycler view
+ * doesn't exist in the layout.
+ */
+ @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a recycler
+ // view, and call this after the template is inflated,
+ // this will not return null.
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ /**
+ * Gets the header view of the recycler layout. This is useful for other mixins if they need to
+ * access views within the header, usually via {@link TemplateLayout#findManagedViewById(int)}.
+ */
+ @SuppressWarnings("NullableProblems") // If clients guarantee that the template has a header,
+ // this call will not return null.
+ public View getHeader() {
+ return mHeader;
+ }
+
+ /**
+ * Recycler mixin needs to update the dividers if the layout direction has changed. This method
+ * should be called when {@link View#onLayout(boolean, int, int, int, int)} of the template
+ * is called.
+ */
+ public void onLayout() {
+ if (mDivider == null) {
+ // Update divider in case layout direction has just been resolved
+ updateDivider();
+ }
+ }
+
+ /**
+ * Gets the adapter of the recycler view in this layout. If the adapter includes a header,
+ * this method will unwrap it and return the underlying adapter.
+ *
+ * @return The adapter, or {@code null} if the recycler view has no adapter.
+ */
+ public Adapter<? extends ViewHolder> getAdapter() {
+ @SuppressWarnings("unchecked") // RecyclerView.getAdapter returns raw type :(
+ final RecyclerView.Adapter<? extends ViewHolder> adapter = mRecyclerView.getAdapter();
+ if (adapter instanceof HeaderAdapter) {
+ return ((HeaderAdapter<? extends ViewHolder>) adapter).getWrappedAdapter();
+ }
+ return adapter;
+ }
+
+ /**
+ * Sets the adapter on the recycler view in this layout.
+ */
+ public void setAdapter(Adapter<? extends ViewHolder> adapter) {
+ mRecyclerView.setAdapter(adapter);
+ }
+
+ /**
+ * Sets the start inset of the divider. This will use the default divider drawable set in the
+ * theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
+ *
+ * @param inset The number of pixels to inset on the "start" side of the list divider. Typically
+ * this will be either {@code @dimen/suw_items_glif_icon_divider_inset} or
+ * {@code @dimen/suw_items_glif_text_divider_inset}.
+ */
+ public void setDividerInset(int inset) {
+ mDividerInset = inset;
+ updateDivider();
+ }
+
+ /**
+ * @return The number of pixels inset on the start side of the divider.
+ */
+ public int getDividerInset() {
+ return mDividerInset;
+ }
+
+ private void updateDivider() {
+ boolean shouldUpdate = true;
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+ shouldUpdate = mTemplateLayout.isLayoutDirectionResolved();
+ }
+ if (shouldUpdate) {
+ if (mDefaultDivider == null) {
+ mDefaultDivider = mDividerDecoration.getDivider();
+ }
+ mDivider = DrawableLayoutDirectionHelper.createRelativeInsetDrawable(
+ mDefaultDivider,
+ mDividerInset /* start */,
+ 0 /* top */,
+ 0 /* end */,
+ 0 /* bottom */,
+ mTemplateLayout);
+ mDividerDecoration.setDivider(mDivider);
+ }
+ }
+
+ /**
+ * @return The drawable used as the divider.
+ */
+ public Drawable getDivider() {
+ return mDivider;
+ }
+
+ /**
+ * Sets the divider item decoration directly. This is a low level method which should be used
+ * only if custom divider behavior is needed, for example if the divider should be shown /
+ * hidden in some specific cases for view holders that cannot implement
+ * {@link com.android.setupwizardlib.DividerItemDecoration.DividedViewHolder}.
+ */
+ public void setDividerItemDecoration(@NonNull DividerItemDecoration decoration) {
+ mRecyclerView.removeItemDecoration(mDividerDecoration);
+ mDividerDecoration = decoration;
+ mRecyclerView.addItemDecoration(mDividerDecoration);
+ updateDivider();
+ }
+}
diff --git a/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java
new file mode 100644
index 0000000..41fb03e
--- /dev/null
+++ b/library/recyclerview/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.setupwizardlib.template;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate;
+
+/**
+ * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link RecyclerView} and
+ * notifies {@link RequireScrollMixin} about scrollability changes.
+ */
+public class RecyclerViewScrollHandlingDelegate implements ScrollHandlingDelegate {
+
+ private static final String TAG = "RVRequireScrollMixin";
+
+ @Nullable
+ private final RecyclerView mRecyclerView;
+
+ @NonNull
+ private final RequireScrollMixin mRequireScrollMixin;
+
+ public RecyclerViewScrollHandlingDelegate(
+ @NonNull RequireScrollMixin requireScrollMixin,
+ @Nullable RecyclerView recyclerView) {
+ mRequireScrollMixin = requireScrollMixin;
+ mRecyclerView = recyclerView;
+ }
+
+ private boolean canScrollDown() {
+ if (mRecyclerView != null) {
+ // Compatibility implementation of View#canScrollVertically
+ final int offset = mRecyclerView.computeVerticalScrollOffset();
+ final int range = mRecyclerView.computeVerticalScrollRange()
+ - mRecyclerView.computeVerticalScrollExtent();
+ return range != 0 && offset < range - 1;
+ }
+ return false;
+ }
+
+ @Override
+ public void startListening() {
+ if (mRecyclerView != null) {
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mRequireScrollMixin.notifyScrollabilityChange(canScrollDown());
+ }
+ });
+
+ if (canScrollDown()) {
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ }
+ } else {
+ Log.w(TAG, "Cannot require scroll. Recycler view is null.");
+ }
+ }
+
+ @Override
+ public void pageScrollDown() {
+ if (mRecyclerView != null) {
+ final int height = mRecyclerView.getHeight();
+ mRecyclerView.smoothScrollBy(0, height);
+ }
+ }
+}