diff --git a/samples/Support7Demos/Android.mk b/samples/Support7Demos/Android.mk index 0e43e3d44..ca8310fea 100644 --- a/samples/Support7Demos/Android.mk +++ b/samples/Support7Demos/Android.mk @@ -26,7 +26,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 4735433c5..4ce63b7e0 100644 --- a/samples/Support7Demos/AndroidManifest.xml +++ b/samples/Support7Demos/AndroidManifest.xml @@ -172,5 +172,15 @@ + + + + + + + + diff --git a/samples/Support7Demos/res/values/strings.xml b/samples/Support7Demos/res/values/strings.xml index 1b136233f..c36809b0d 100644 --- a/samples/Support7Demos/res/values/strings.xml +++ b/samples/Support7Demos/res/values/strings.xml @@ -95,4 +95,5 @@ Local Playback Local Playback on Presentation Display + RecyclerView 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 000000000..7497c9a8d --- /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; + } + } +}