Example activity for RecyclerView
Also includes a very basic custom RecyclerView LayoutManager.
Change-Id: Ic6dca39a4e71dbe8b3b82965299bc554fad18a43
diff --git a/samples/Support7Demos/Android.mk b/samples/Support7Demos/Android.mk
index 0e43e3d..ca8310f 100644
--- a/samples/Support7Demos/Android.mk
+++ b/samples/Support7Demos/Android.mk
@@ -26,7 +26,8 @@
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-gridlayout \
- android-support-v7-mediarouter
+ android-support-v7-mediarouter \
+ android-support-v7-recyclerview
LOCAL_RESOURCE_DIR = \
$(LOCAL_PATH)/res \
frameworks/support/v7/appcompat/res \
diff --git a/samples/Support7Demos/AndroidManifest.xml b/samples/Support7Demos/AndroidManifest.xml
index 4735433..4ce63b7 100644
--- a/samples/Support7Demos/AndroidManifest.xml
+++ b/samples/Support7Demos/AndroidManifest.xml
@@ -172,5 +172,15 @@
</intent-filter>
</activity>
+ <!-- RecyclerView samples -->
+ <activity android:name=".widget.RecyclerViewActivity"
+ android:label="@string/recycler_view"
+ android:theme="@style/Theme.AppCompat">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/samples/Support7Demos/res/values/strings.xml b/samples/Support7Demos/res/values/strings.xml
index 1b13623..c36809b 100644
--- a/samples/Support7Demos/res/values/strings.xml
+++ b/samples/Support7Demos/res/values/strings.xml
@@ -95,4 +95,5 @@
<string name="sample_media_route_activity_local">Local Playback</string>
<string name="sample_media_route_activity_presentation">Local Playback on Presentation Display</string>
+ <string name="recycler_view">RecyclerView</string>
</resources>
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/widget/RecyclerViewActivity.java b/samples/Support7Demos/src/com/example/android/supportv7/widget/RecyclerViewActivity.java
new file mode 100644
index 0000000..7497c9a
--- /dev/null
+++ b/samples/Support7Demos/src/com/example/android/supportv7/widget/RecyclerViewActivity.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2013 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.example.android.supportv7.widget;
+
+import android.R;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.widget.TextView;
+
+public class RecyclerViewActivity extends Activity {
+ private RecyclerView mRecyclerView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final RecyclerView rv = new RecyclerView(this);
+ rv.setLayoutManager(new MyLayoutManager(this));
+ rv.setHasFixedSize(true);
+ rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ rv.setAdapter(new MyAdapter());
+ setContentView(rv);
+
+ mRecyclerView = rv;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ mRecyclerView.requestLayout();
+ return super.onOptionsItemSelected(item);
+ }
+
+ private static final int SCROLL_DISTANCE = 80; // dp
+
+ /**
+ * A basic ListView-style LayoutManager.
+ */
+ class MyLayoutManager extends RecyclerView.LayoutManager {
+ private static final String TAG = "MyLayoutManager";
+ private int mFirstPosition;
+ private final int mScrollDistance;
+
+ public MyLayoutManager(Context c) {
+ final DisplayMetrics dm = c.getResources().getDisplayMetrics();
+ mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f);
+ }
+
+ @Override
+ public void layoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) {
+ final RecyclerView parent = getRecyclerView();
+ final int parentBottom = parent.getHeight() - parent.getPaddingBottom();
+
+ recycler.scrapAllViewsAttached();
+
+ int top = parent.getPaddingTop();
+ int bottom;
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+
+ final int count = adapter.getItemCount();
+ for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) {
+ View v = recycler.getViewForPosition(mFirstPosition + i);
+ addView(v, i);
+ v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ bottom = top + v.getMeasuredHeight();
+ v.layout(left, top, right, bottom);
+ }
+
+ recycler.detachDirtyScrapViews();
+ }
+
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler) {
+ final RecyclerView parent = getRecyclerView();
+ if (parent.getChildCount() == 0) {
+ return 0;
+ }
+
+ int scrolled = 0;
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+ if (dy < 0) {
+ while (scrolled > dy) {
+ final View topView = parent.getChildAt(0);
+ final int hangingTop = Math.max(-topView.getTop(), 0);
+ final int scrollBy = Math.min(scrolled - dy, hangingTop);
+ scrolled -= scrollBy;
+ parent.offsetChildrenVertical(scrollBy);
+ if (mFirstPosition > 0 && scrolled > dy) {
+ mFirstPosition--;
+ View v = recycler.getViewForPosition(mFirstPosition);
+ addView(v, 0);
+ v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ final int bottom = topView.getTop(); // TODO decorated top?
+ final int top = bottom - v.getMeasuredHeight();
+ v.layout(left, top, right, bottom);
+ } else {
+ break;
+ }
+ }
+ } else if (dy > 0) {
+ final int parentHeight = parent.getHeight();
+ while (scrolled < dy) {
+ final View bottomView = parent.getChildAt(parent.getChildCount() - 1);
+ final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0);
+ final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
+ scrolled -= scrollBy;
+ parent.offsetChildrenVertical(scrollBy);
+ if (scrolled < dy &&
+ parent.getAdapter().getItemCount() > mFirstPosition + parent.getChildCount()) {
+ View v = recycler.getViewForPosition(
+ mFirstPosition + parent.getChildCount());
+ final int top = parent.getChildAt(parent.getChildCount() - 1).getBottom();
+ addView(v);
+ v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ final int bottom = top + v.getMeasuredHeight();
+ v.layout(left, top, right, bottom);
+ } else {
+ break;
+ }
+ }
+ }
+ detachAndScrapViewsOutOfBounds(recycler);
+ return scrolled;
+ }
+
+ @Override
+ public View onFocusSearchFailed(View focused, int direction,
+ RecyclerView.Recycler recycler) {
+ final RecyclerView rv = getRecyclerView();
+ final int oldFirstPosition = mFirstPosition;
+ final int oldCount = rv.getChildCount();
+
+ if (oldCount == 0) {
+ return null;
+ }
+
+ final int left = rv.getPaddingLeft();
+ final int right = rv.getWidth() - rv.getPaddingRight();
+
+ View toFocus = null;
+ int newViewsHeight = 0;
+ if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) {
+ while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) {
+ mFirstPosition--;
+ View v = recycler.getViewForPosition(mFirstPosition);
+ final int bottom = rv.getChildAt(0).getTop(); // TODO decorated top?
+ addView(v, 0);
+ v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ final int top = bottom - v.getMeasuredHeight();
+ v.layout(left, top, right, bottom);
+ if (v.isFocusable()) {
+ toFocus = v;
+ break;
+ }
+ }
+ }
+ if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) {
+ while (mFirstPosition + rv.getChildCount() < rv.getAdapter().getItemCount() &&
+ newViewsHeight < mScrollDistance) {
+ View v = recycler.getViewForPosition(mFirstPosition + rv.getChildCount());
+ final int top = rv.getChildAt(rv.getChildCount() - 1).getBottom();
+ addView(v);
+ v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ final int bottom = top + v.getMeasuredHeight();
+ v.layout(left, top, right, bottom);
+ if (v.isFocusable()) {
+ toFocus = v;
+ break;
+ }
+ }
+ }
+
+ return toFocus;
+ }
+
+ public void detachAndScrapViewsOutOfBounds(RecyclerView.Recycler recycler) {
+ final RecyclerView parent = getRecyclerView();
+ final int childCount = parent.getChildCount();
+ final int parentWidth = parent.getWidth();
+ final int parentHeight = parent.getHeight();
+ boolean foundFirst = false;
+ int first = 0;
+ int last = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View v = parent.getChildAt(i);
+ if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
+ v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
+ if (!foundFirst) {
+ first = i;
+ foundFirst = true;
+ }
+ last = i;
+ }
+ }
+ for (int i = childCount - 1; i > last; i--) {
+ recycler.detachAndScrapView(parent.getChildAt(i));
+ }
+ for (int i = 0; i < first; i++) {
+ recycler.detachAndScrapView(parent.getChildAt(i));
+ }
+ if (parent.getChildCount() == 0) {
+ mFirstPosition = 0;
+ } else {
+ mFirstPosition += first;
+ }
+ }
+ }
+
+ class MyAdapter extends RecyclerView.Adapter {
+ private int mBackground;
+
+ public MyAdapter() {
+ TypedValue val = new TypedValue();
+ RecyclerViewActivity.this.getTheme().resolveAttribute(
+ R.attr.selectableItemBackground, val, true);
+ mBackground = val.resourceId;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType) {
+ ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this));
+ h.textView.setMinimumHeight(128);
+ h.textView.setFocusable(true);
+ h.textView.setBackgroundResource(mBackground);
+ return h;
+ }
+
+ @Override
+ public void bindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ ((ViewHolder) holder).textView.setText("Item " + position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return 100;
+ }
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView textView;
+
+ public ViewHolder(TextView v) {
+ super(v);
+ textView = v;
+ }
+ }
+}