| /* |
| * Copyright (C) 2015 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.items; |
| |
| import android.content.res.TypedArray; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.LayerDrawable; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.recyclerview.widget.RecyclerView; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import com.android.setupwizardlib.R; |
| |
| /** |
| * An adapter used with RecyclerView to display an {@link ItemHierarchy}. The item hierarchy used to |
| * create this adapter can be inflated by {@link com.android.setupwizardlib.items.ItemInflater} from |
| * XML. |
| */ |
| public class RecyclerItemAdapter extends RecyclerView.Adapter<ItemViewHolder> |
| implements ItemHierarchy.Observer { |
| |
| private static final String TAG = "RecyclerItemAdapter"; |
| |
| /** |
| * A view tag set by {@link View#setTag(Object)}. If set on the root view of a layout, it will not |
| * create the default background for the list item. This means the item will not have ripple touch |
| * feedback by default. |
| */ |
| public static final String TAG_NO_BACKGROUND = "noBackground"; |
| |
| /** Listener for item selection in this adapter. */ |
| public interface OnItemSelectedListener { |
| |
| /** |
| * Called when an item in this adapter is clicked. |
| * |
| * @param item The Item corresponding to the position being clicked. |
| */ |
| void onItemSelected(IItem item); |
| } |
| |
| private final ItemHierarchy itemHierarchy; |
| private OnItemSelectedListener listener; |
| |
| public RecyclerItemAdapter(ItemHierarchy hierarchy) { |
| itemHierarchy = hierarchy; |
| itemHierarchy.registerObserver(this); |
| } |
| |
| /** |
| * Gets the item at the given position. |
| * |
| * @see ItemHierarchy#getItemAt(int) |
| */ |
| public IItem getItem(int position) { |
| return itemHierarchy.getItemAt(position); |
| } |
| |
| @Override |
| public long getItemId(int position) { |
| IItem mItem = getItem(position); |
| if (mItem instanceof AbstractItem) { |
| final int id = ((AbstractItem) mItem).getId(); |
| return id > 0 ? id : RecyclerView.NO_ID; |
| } else { |
| return RecyclerView.NO_ID; |
| } |
| } |
| |
| @Override |
| public int getItemCount() { |
| return itemHierarchy.getCount(); |
| } |
| |
| @Override |
| public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); |
| final View view = inflater.inflate(viewType, parent, false); |
| final ItemViewHolder viewHolder = new ItemViewHolder(view); |
| |
| final Object viewTag = view.getTag(); |
| if (!TAG_NO_BACKGROUND.equals(viewTag)) { |
| final TypedArray typedArray = |
| parent.getContext().obtainStyledAttributes(R.styleable.SuwRecyclerItemAdapter); |
| Drawable selectableItemBackground = |
| typedArray.getDrawable( |
| R.styleable.SuwRecyclerItemAdapter_android_selectableItemBackground); |
| if (selectableItemBackground == null) { |
| selectableItemBackground = |
| typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_selectableItemBackground); |
| } |
| |
| Drawable background = view.getBackground(); |
| if (background == null) { |
| background = |
| typedArray.getDrawable(R.styleable.SuwRecyclerItemAdapter_android_colorBackground); |
| } |
| |
| if (selectableItemBackground == null || background == null) { |
| Log.e( |
| TAG, |
| "Cannot resolve required attributes." |
| + " selectableItemBackground=" |
| + selectableItemBackground |
| + " background=" |
| + background); |
| } else { |
| final Drawable[] layers = {background, selectableItemBackground}; |
| view.setBackgroundDrawable(new PatchedLayerDrawable(layers)); |
| } |
| |
| typedArray.recycle(); |
| } |
| |
| view.setOnClickListener( |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| final IItem item = viewHolder.getItem(); |
| if (listener != null && item != null && item.isEnabled()) { |
| listener.onItemSelected(item); |
| } |
| } |
| }); |
| |
| return viewHolder; |
| } |
| |
| @Override |
| public void onBindViewHolder(ItemViewHolder holder, int position) { |
| final IItem item = getItem(position); |
| holder.setEnabled(item.isEnabled()); |
| holder.setItem(item); |
| item.onBindView(holder.itemView); |
| } |
| |
| @Override |
| public int getItemViewType(int position) { |
| // Use layout resource as item view type. RecyclerView item type does not have to be |
| // contiguous. |
| IItem item = getItem(position); |
| return item.getLayoutResource(); |
| } |
| |
| @Override |
| public void onChanged(ItemHierarchy hierarchy) { |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public void onItemRangeChanged(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { |
| notifyItemRangeChanged(positionStart, itemCount); |
| } |
| |
| @Override |
| public void onItemRangeInserted(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { |
| notifyItemRangeInserted(positionStart, itemCount); |
| } |
| |
| @Override |
| public void onItemRangeMoved( |
| ItemHierarchy itemHierarchy, int fromPosition, int toPosition, int itemCount) { |
| // There is no notifyItemRangeMoved |
| // https://code.google.com/p/android/issues/detail?id=125984 |
| if (itemCount == 1) { |
| notifyItemMoved(fromPosition, toPosition); |
| } else { |
| // If more than one, degenerate into the catch-all data set changed callback, since I'm |
| // not sure how recycler view handles multiple calls to notifyItemMoved (if the result |
| // is committed after every notification then naively calling |
| // notifyItemMoved(from + i, to + i) is wrong). |
| // Logging this in case this is a more common occurrence than expected. |
| Log.i(TAG, "onItemRangeMoved with more than one item"); |
| notifyDataSetChanged(); |
| } |
| } |
| |
| @Override |
| public void onItemRangeRemoved(ItemHierarchy itemHierarchy, int positionStart, int itemCount) { |
| notifyItemRangeRemoved(positionStart, itemCount); |
| } |
| |
| /** |
| * Find an item hierarchy within the root hierarchy. |
| * |
| * @see ItemHierarchy#findItemById(int) |
| */ |
| public ItemHierarchy findItemById(int id) { |
| return itemHierarchy.findItemById(id); |
| } |
| |
| /** Gets the root item hierarchy in this adapter. */ |
| public ItemHierarchy getRootItemHierarchy() { |
| return itemHierarchy; |
| } |
| |
| /** |
| * Sets the listener to listen for when user clicks on a item. |
| * |
| * @see OnItemSelectedListener |
| */ |
| public void setOnItemSelectedListener(OnItemSelectedListener listener) { |
| this.listener = listener; |
| } |
| |
| /** |
| * Before Lollipop, LayerDrawable always return true in getPadding, even if the children layers do |
| * not have any padding. Patch the implementation so that getPadding returns false if the padding |
| * is empty. |
| * |
| * <p>When getPadding is true, the padding of the view will be replaced by the padding of the |
| * drawable when {@link View#setBackgroundDrawable(Drawable)} is called. This patched class makes |
| * sure layer drawables without padding does not clear out original padding on the view. |
| */ |
| @VisibleForTesting |
| static class PatchedLayerDrawable extends LayerDrawable { |
| |
| /** {@inheritDoc} */ |
| PatchedLayerDrawable(Drawable[] layers) { |
| super(layers); |
| } |
| |
| @Override |
| public boolean getPadding(Rect padding) { |
| final boolean superHasPadding = super.getPadding(padding); |
| return superHasPadding |
| && !(padding.left == 0 && padding.top == 0 && padding.right == 0 && padding.bottom == 0); |
| } |
| } |
| } |