RecyclerViewActivity.java revision 63c8ab9435d707a23e2045af3be410ea7deed895
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18package com.example.android.supportv7.widget; 19 20import android.R; 21import android.app.Activity; 22import android.content.Context; 23import android.os.Bundle; 24import android.support.v4.view.MenuItemCompat; 25import android.support.v7.widget.RecyclerView; 26import android.util.DisplayMetrics; 27import android.util.TypedValue; 28import android.view.Menu; 29import android.view.MenuItem; 30import android.view.View; 31import android.view.ViewGroup; 32import android.view.View.MeasureSpec; 33import android.widget.TextView; 34import com.example.android.supportv7.Cheeses; 35 36import java.util.ArrayList; 37import java.util.Collections; 38 39public class RecyclerViewActivity extends Activity { 40 private RecyclerView mRecyclerView; 41 42 @Override 43 protected void onCreate(Bundle savedInstanceState) { 44 super.onCreate(savedInstanceState); 45 46 final RecyclerView rv = new RecyclerView(this); 47 rv.setLayoutManager(new MyLayoutManager(this)); 48 rv.setHasFixedSize(true); 49 rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 50 ViewGroup.LayoutParams.MATCH_PARENT)); 51 rv.setAdapter(new MyAdapter(Cheeses.sCheeseStrings)); 52 setContentView(rv); 53 54 mRecyclerView = rv; 55 } 56 57 @Override 58 public boolean onCreateOptionsMenu(Menu menu) { 59 super.onCreateOptionsMenu(menu); 60 MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); 61 return true; 62 } 63 64 @Override 65 public boolean onOptionsItemSelected(MenuItem item) { 66 mRecyclerView.requestLayout(); 67 return super.onOptionsItemSelected(item); 68 } 69 70 private static final int SCROLL_DISTANCE = 80; // dp 71 72 /** 73 * A basic ListView-style LayoutManager. 74 */ 75 class MyLayoutManager extends RecyclerView.LayoutManager { 76 private static final String TAG = "MyLayoutManager"; 77 private int mFirstPosition; 78 private final int mScrollDistance; 79 80 public MyLayoutManager(Context c) { 81 final DisplayMetrics dm = c.getResources().getDisplayMetrics(); 82 mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f); 83 } 84 85 @Override 86 public void layoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) { 87 final int parentBottom = getHeight() - getPaddingBottom(); 88 89 final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null; 90 int oldTop = getPaddingTop(); 91 if (oldTopView != null) { 92 oldTop = oldTopView.getTop(); 93 } 94 95 detachAndScrapAttachedViews(recycler); 96 97 int top = oldTop; 98 int bottom; 99 final int left = getPaddingLeft(); 100 final int right = getWidth() - getPaddingRight(); 101 102 final int count = adapter.getItemCount(); 103 for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) { 104 View v = recycler.getViewForPosition(adapter, mFirstPosition + i); 105 addView(v, i); 106 measureChildWithMargins(v, 0, 0); 107 bottom = top + v.getMeasuredHeight(); 108 v.layout(left, top, right, bottom); 109 } 110 111 removeAndRecycleScrap(recycler); 112 } 113 114 @Override 115 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 116 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 117 ViewGroup.LayoutParams.WRAP_CONTENT); 118 } 119 120 @Override 121 public boolean canScrollVertically() { 122 return true; 123 } 124 125 @Override 126 public int scrollVerticallyBy(int dy, RecyclerView.Adapter adapter, 127 RecyclerView.Recycler recycler) { 128 if (getChildCount() == 0) { 129 return 0; 130 } 131 132 int scrolled = 0; 133 final int left = getPaddingLeft(); 134 final int right = getWidth() - getPaddingRight(); 135 if (dy < 0) { 136 while (scrolled > dy) { 137 final View topView = getChildAt(0); 138 final int hangingTop = Math.max(-topView.getTop(), 0); 139 final int scrollBy = Math.min(scrolled - dy, hangingTop); 140 scrolled -= scrollBy; 141 offsetChildrenVertical(scrollBy); 142 if (mFirstPosition > 0 && scrolled > dy) { 143 mFirstPosition--; 144 View v = recycler.getViewForPosition(adapter, mFirstPosition); 145 addView(v, 0); 146 measureChildWithMargins(v, 0, 0); 147 final int bottom = topView.getTop(); // TODO decorated top? 148 final int top = bottom - v.getMeasuredHeight(); 149 v.layout(left, top, right, bottom); 150 } else { 151 break; 152 } 153 } 154 } else if (dy > 0) { 155 final int parentHeight = getHeight(); 156 while (scrolled < dy) { 157 final View bottomView = getChildAt(getChildCount() - 1); 158 final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0); 159 final int scrollBy = -Math.min(dy - scrolled, hangingBottom); 160 scrolled -= scrollBy; 161 offsetChildrenVertical(scrollBy); 162 if (scrolled < dy && getItemCount() > mFirstPosition + getChildCount()) { 163 View v = recycler.getViewForPosition(adapter, 164 mFirstPosition + getChildCount()); 165 final int top = getChildAt(getChildCount() - 1).getBottom(); 166 addView(v); 167 measureChildWithMargins(v, 0, 0); 168 final int bottom = top + v.getMeasuredHeight(); 169 v.layout(left, top, right, bottom); 170 } else { 171 break; 172 } 173 } 174 } 175 recycleViewsOutOfBounds(recycler); 176 return scrolled; 177 } 178 179 @Override 180 public View onFocusSearchFailed(View focused, int direction, 181 RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) { 182 final int oldCount = getChildCount(); 183 184 if (oldCount == 0) { 185 return null; 186 } 187 188 final int left = getPaddingLeft(); 189 final int right = getWidth() - getPaddingRight(); 190 191 View toFocus = null; 192 int newViewsHeight = 0; 193 if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) { 194 while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) { 195 mFirstPosition--; 196 View v = recycler.getViewForPosition(adapter, mFirstPosition); 197 final int bottom = getChildAt(0).getTop(); // TODO decorated top? 198 addView(v, 0); 199 measureChildWithMargins(v, 0, 0); 200 final int top = bottom - v.getMeasuredHeight(); 201 v.layout(left, top, right, bottom); 202 if (v.isFocusable()) { 203 toFocus = v; 204 break; 205 } 206 } 207 } 208 if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) { 209 while (mFirstPosition + getChildCount() < getItemCount() && 210 newViewsHeight < mScrollDistance) { 211 View v = recycler.getViewForPosition(adapter, mFirstPosition + getChildCount()); 212 final int top = getChildAt(getChildCount() - 1).getBottom(); 213 addView(v); 214 measureChildWithMargins(v, 0, 0); 215 final int bottom = top + v.getMeasuredHeight(); 216 v.layout(left, top, right, bottom); 217 if (v.isFocusable()) { 218 toFocus = v; 219 break; 220 } 221 } 222 } 223 224 return toFocus; 225 } 226 227 public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) { 228 final int childCount = getChildCount(); 229 final int parentWidth = getWidth(); 230 final int parentHeight = getHeight(); 231 boolean foundFirst = false; 232 int first = 0; 233 int last = 0; 234 for (int i = 0; i < childCount; i++) { 235 final View v = getChildAt(i); 236 if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth && 237 v.getBottom() >= 0 && v.getTop() <= parentHeight)) { 238 if (!foundFirst) { 239 first = i; 240 foundFirst = true; 241 } 242 last = i; 243 } 244 } 245 for (int i = childCount - 1; i > last; i--) { 246 removeAndRecycleViewAt(i, recycler); 247 } 248 for (int i = first - 1; i >= 0; i--) { 249 removeAndRecycleViewAt(i, recycler); 250 } 251 if (getChildCount() == 0) { 252 mFirstPosition = 0; 253 } else { 254 mFirstPosition += first; 255 } 256 } 257 } 258 259 class MyAdapter extends RecyclerView.Adapter<ViewHolder> { 260 private int mBackground; 261 private ArrayList<String> mValues; 262 263 public MyAdapter(String[] strings) { 264 TypedValue val = new TypedValue(); 265 RecyclerViewActivity.this.getTheme().resolveAttribute( 266 R.attr.selectableItemBackground, val, true); 267 mBackground = val.resourceId; 268 mValues = new ArrayList<String>(); 269 Collections.addAll(mValues, strings); 270 } 271 272 @Override 273 public ViewHolder createViewHolder(ViewGroup parent, int viewType) { 274 final ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this)); 275 h.textView.setMinimumHeight(128); 276 h.textView.setFocusable(true); 277 h.textView.setBackgroundResource(mBackground); 278 h.textView.setOnClickListener(new View.OnClickListener() { 279 @Override 280 public void onClick(View v) { 281 final int pos = h.getPosition(); 282 if (mValues.size() > pos + 1) { 283 final String t = mValues.get(pos); 284 mValues.set(pos, mValues.get(pos + 1)); 285 mValues.set(pos + 1, t); 286 notifyItemRangeChanged(pos, 2); 287 } 288 } 289 }); 290 return h; 291 } 292 293 @Override 294 public void bindViewHolder(ViewHolder holder, int position) { 295 holder.textView.setText(mValues.get(position)); 296 } 297 298 @Override 299 public int getItemCount() { 300 return mValues.size(); 301 } 302 } 303 304 static class ViewHolder extends RecyclerView.ViewHolder { 305 public TextView textView; 306 307 public ViewHolder(TextView v) { 308 super(v); 309 textView = v; 310 } 311 } 312} 313