From a74bc1d5c6d7cb9e0f5add4c56a983cb492cb3c2 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Thu, 22 Oct 2015 11:21:19 -0700 Subject: [PATCH] [SuwLib] Recycler item adapter support Implement RecyclerView item adapter for an ItemHierarchy. Change-Id: Id7e650fafa467ca7ee3c80102efc73d4323e5b1c --- library/Android.mk | 2 +- .../items/RecyclerItemAdapter.java | 141 ++++++++++++++++++ .../test/RecyclerItemAdapterTest.java | 67 +++++++++ library/rules.gradle | 6 +- 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java create mode 100644 library/full-support/test/src/com/android/setupwizardlib/test/RecyclerItemAdapterTest.java diff --git a/library/Android.mk b/library/Android.mk index bcbd0e9..543b840 100644 --- a/library/Android.mk +++ b/library/Android.mk @@ -58,7 +58,7 @@ LOCAL_RESOURCE_DIR := \ frameworks/support/v7/appcompat/res \ frameworks/support/v7/recyclerview/res LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under, main/src eclair-mr1/src) +LOCAL_SRC_FILES := $(call all-java-files-under, main/src eclair-mr1/src full-support/src) LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-v4 \ android-support-v7-appcompat \ diff --git a/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java b/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java new file mode 100644 index 0000000..314314e --- /dev/null +++ b/library/full-support/src/com/android/setupwizardlib/items/RecyclerItemAdapter.java @@ -0,0 +1,141 @@ +/* + * 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.support.v7.widget.RecyclerView; +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 + implements ItemHierarchy.Observer { + + private static final int[] SELECTABLE_ITEM_BACKGROUND = new int[] { + R.attr.selectableItemBackground + }; + + public interface OnItemSelectedListener { + void onItemSelected(IItem item); + } + + private static class GenericViewHolder extends RecyclerView.ViewHolder { + + public GenericViewHolder(View itemView) { + super(itemView); + } + } + + private final ItemHierarchy mItemHierarchy; + private OnItemSelectedListener mListener; + + public RecyclerItemAdapter(ItemHierarchy hierarchy) { + mItemHierarchy = hierarchy; + mItemHierarchy.registerObserver(this); + setHasStableIds(true); + } + + public IItem getItem(int position) { + return mItemHierarchy.getItemAt(position); + } + + @Override + public long getItemId(int position) { + IItem mItem = getItem(position); + if (mItem instanceof AbstractItem) { + return ((AbstractItem) mItem).getId(); + } else { + return RecyclerView.NO_ID; + } + } + + @Override + public int getItemCount() { + return mItemHierarchy.getCount(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final View view = inflater.inflate(viewType, parent, false); + final GenericViewHolder viewHolder = new GenericViewHolder(view); + + final TypedArray typedArray = parent.getContext() + .obtainStyledAttributes(SELECTABLE_ITEM_BACKGROUND); + view.setBackgroundDrawable(typedArray.getDrawable(0)); + typedArray.recycle(); + + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final IItem item = getItem(viewHolder.getAdapterPosition()); + if (mListener != null && item.isEnabled()) { + mListener.onItemSelected(item); + } + } + }); + + return viewHolder; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final IItem item = getItem(position); + item.onBindView(holder.itemView); + if (item.isEnabled()) { + holder.itemView.setClickable(true); + holder.itemView.setEnabled(true); + holder.itemView.setFocusable(true); + } else { + holder.itemView.setClickable(false); + holder.itemView.setEnabled(false); + holder.itemView.setFocusable(false); + } + } + + @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(); + } + + public ItemHierarchy findItemById(int id) { + return mItemHierarchy.findItemById(id); + } + + public ItemHierarchy getRootItemHierarchy() { + return mItemHierarchy; + } + + public void setOnItemSelectedListener(OnItemSelectedListener listener) { + mListener = listener; + } +} diff --git a/library/full-support/test/src/com/android/setupwizardlib/test/RecyclerItemAdapterTest.java b/library/full-support/test/src/com/android/setupwizardlib/test/RecyclerItemAdapterTest.java new file mode 100644 index 0000000..d3061b1 --- /dev/null +++ b/library/full-support/test/src/com/android/setupwizardlib/test/RecyclerItemAdapterTest.java @@ -0,0 +1,67 @@ +/* + * 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.test; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.setupwizardlib.items.Item; +import com.android.setupwizardlib.items.ItemGroup; +import com.android.setupwizardlib.items.ItemHierarchy; +import com.android.setupwizardlib.items.RecyclerItemAdapter; + +import java.util.Arrays; +import java.util.HashSet; + +public class RecyclerItemAdapterTest extends AndroidTestCase { + + private Item[] mItems = new Item[5]; + private ItemGroup mItemGroup = new ItemGroup(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + for (int i = 0; i < 5; i++) { + Item item = new Item(); + item.setTitle("TestTitle" + i); + item.setId(i); + // Layout resource: 0 -> 1, 1 -> 11, 2 -> 21, 3 -> 1, 4 -> 11. + // (Resource IDs cannot be 0) + item.setLayoutResource((i % 3) * 10 + 1); + mItems[i] = item; + mItemGroup.addChild(item); + } + } + + @SmallTest + public void testAdapter() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + assertEquals("Adapter should have 5 items", 5, adapter.getItemCount()); + assertEquals("Adapter should return the first item", mItems[0], adapter.getItem(0)); + assertEquals("ID should be same as position", 2, adapter.getItemId(2)); + + // ViewType is same as layout resource for RecyclerItemAdapter + assertEquals("Second item should have view type 21", 21, adapter.getItemViewType(2)); + } + + @SmallTest + public void testGetRootItemHierarchy() { + RecyclerItemAdapter adapter = new RecyclerItemAdapter(mItemGroup); + ItemHierarchy root = adapter.getRootItemHierarchy(); + assertSame("Root item hierarchy should be mItemGroup", mItemGroup, root); + } +} diff --git a/library/rules.gradle b/library/rules.gradle index 8021372..7660853 100644 --- a/library/rules.gradle +++ b/library/rules.gradle @@ -86,7 +86,7 @@ android { } fullSupport { - java.srcDirs = ['eclair-mr1/src'] + java.srcDirs = ['eclair-mr1/src', 'full-support/src'] res.srcDirs = ['eclair-mr1/res', 'full-support/res'] } @@ -95,5 +95,9 @@ android { java.srcDirs = ['test/src'] res.srcDirs = ['test/res'] } + + androidTestFullSupport { + java.srcDirs = ['full-support/test/src'] + } } }