diff options
author | Maurice Lam <yukl@google.com> | 2017-03-28 12:48:40 -0700 |
---|---|---|
committer | Maurice Lam <yukl@google.com> | 2017-03-28 14:23:21 -0700 |
commit | 83862bb59558fc044de9aa0d6e9407be53af8b81 (patch) | |
tree | 032855cc188420699c048478a6a24c44731a3151 /library/recyclerview/src/com/android/setupwizardlib/template | |
parent | 9955331ed7bda114488b1a4701456ec478ff63bf (diff) | |
download | setupwizard-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')
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); + } + } +} |