RecyclerViewActivity.java revision 549b58504ef397f4c1524ec370bbd0569027e315
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.graphics.Rect; 24import android.os.Bundle; 25import android.support.v4.view.MenuItemCompat; 26import android.support.v7.widget.RecyclerView; 27import android.util.DisplayMetrics; 28import android.util.TypedValue; 29import android.view.Menu; 30import android.view.MenuItem; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.View.MeasureSpec; 34import android.widget.TextView; 35import com.example.android.supportv7.Cheeses; 36 37import java.util.ArrayList; 38import java.util.Collections; 39 40public class RecyclerViewActivity extends Activity { 41 private RecyclerView mRecyclerView; 42 43 @Override 44 protected void onCreate(Bundle savedInstanceState) { 45 super.onCreate(savedInstanceState); 46 47 final RecyclerView rv = new RecyclerView(this); 48 rv.setLayoutManager(new MyLayoutManager(this)); 49 rv.setHasFixedSize(true); 50 rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 51 ViewGroup.LayoutParams.MATCH_PARENT)); 52 rv.setAdapter(new MyAdapter(Cheeses.sCheeseStrings)); 53 setContentView(rv); 54 55 mRecyclerView = rv; 56 } 57 58 @Override 59 public boolean onCreateOptionsMenu(Menu menu) { 60 super.onCreateOptionsMenu(menu); 61 MenuItemCompat.setShowAsAction(menu.add("Layout"), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); 62 return true; 63 } 64 65 @Override 66 public boolean onOptionsItemSelected(MenuItem item) { 67 mRecyclerView.requestLayout(); 68 return super.onOptionsItemSelected(item); 69 } 70 71 private static final int SCROLL_DISTANCE = 80; // dp 72 73 /** 74 * A basic ListView-style LayoutManager. 75 */ 76 class MyLayoutManager extends RecyclerView.LayoutManager { 77 private static final String TAG = "MyLayoutManager"; 78 private int mFirstPosition; 79 private final int mScrollDistance; 80 81 public MyLayoutManager(Context c) { 82 final DisplayMetrics dm = c.getResources().getDisplayMetrics(); 83 mScrollDistance = (int) (SCROLL_DISTANCE * dm.density + 0.5f); 84 } 85 86 @Override 87 public void layoutChildren(RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) { 88 final RecyclerView parent = getRecyclerView(); 89 final int parentBottom = parent.getHeight() - parent.getPaddingBottom(); 90 91 final View oldTopView = parent.getChildCount() > 0 ? parent.getChildAt(0) : null; 92 int oldTop = parent.getPaddingTop(); 93 if (oldTopView != null) { 94 oldTop = oldTopView.getTop(); 95 } 96 97 recycler.scrapAllViewsAttached(); 98 99 int top = oldTop; 100 int bottom; 101 final int left = parent.getPaddingLeft(); 102 final int right = parent.getWidth() - parent.getPaddingRight(); 103 104 final int count = adapter.getItemCount(); 105 for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) { 106 View v = recycler.getViewForPosition(mFirstPosition + i); 107 addView(v, i); 108 v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 109 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 110 bottom = top + v.getMeasuredHeight(); 111 v.layout(left, top, right, bottom); 112 } 113 114 recycler.detachDirtyScrapViews(); 115 } 116 117 @Override 118 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 119 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 120 ViewGroup.LayoutParams.WRAP_CONTENT); 121 } 122 123 @Override 124 public boolean canScrollVertically() { 125 return true; 126 } 127 128 @Override 129 public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler) { 130 final RecyclerView parent = getRecyclerView(); 131 if (parent.getChildCount() == 0) { 132 return 0; 133 } 134 135 int scrolled = 0; 136 final int left = parent.getPaddingLeft(); 137 final int right = parent.getWidth() - parent.getPaddingRight(); 138 if (dy < 0) { 139 while (scrolled > dy) { 140 final View topView = parent.getChildAt(0); 141 final int hangingTop = Math.max(-topView.getTop(), 0); 142 final int scrollBy = Math.min(scrolled - dy, hangingTop); 143 scrolled -= scrollBy; 144 parent.offsetChildrenVertical(scrollBy); 145 if (mFirstPosition > 0 && scrolled > dy) { 146 mFirstPosition--; 147 View v = recycler.getViewForPosition(mFirstPosition); 148 addView(v, 0); 149 v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 150 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 151 final int bottom = topView.getTop(); // TODO decorated top? 152 final int top = bottom - v.getMeasuredHeight(); 153 v.layout(left, top, right, bottom); 154 } else { 155 break; 156 } 157 } 158 } else if (dy > 0) { 159 final int parentHeight = parent.getHeight(); 160 while (scrolled < dy) { 161 final View bottomView = parent.getChildAt(parent.getChildCount() - 1); 162 final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0); 163 final int scrollBy = -Math.min(dy - scrolled, hangingBottom); 164 scrolled -= scrollBy; 165 parent.offsetChildrenVertical(scrollBy); 166 if (scrolled < dy && 167 parent.getAdapter().getItemCount() > mFirstPosition + parent.getChildCount()) { 168 View v = recycler.getViewForPosition( 169 mFirstPosition + parent.getChildCount()); 170 final int top = parent.getChildAt(parent.getChildCount() - 1).getBottom(); 171 addView(v); 172 v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 173 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 174 final int bottom = top + v.getMeasuredHeight(); 175 v.layout(left, top, right, bottom); 176 } else { 177 break; 178 } 179 } 180 } 181 detachAndScrapViewsOutOfBounds(recycler); 182 return scrolled; 183 } 184 185 @Override 186 public View onFocusSearchFailed(View focused, int direction, 187 RecyclerView.Recycler recycler) { 188 final RecyclerView rv = getRecyclerView(); 189 final int oldFirstPosition = mFirstPosition; 190 final int oldCount = rv.getChildCount(); 191 192 if (oldCount == 0) { 193 return null; 194 } 195 196 final int left = rv.getPaddingLeft(); 197 final int right = rv.getWidth() - rv.getPaddingRight(); 198 199 View toFocus = null; 200 int newViewsHeight = 0; 201 if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) { 202 while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) { 203 mFirstPosition--; 204 View v = recycler.getViewForPosition(mFirstPosition); 205 final int bottom = rv.getChildAt(0).getTop(); // TODO decorated top? 206 addView(v, 0); 207 v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 208 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 209 final int top = bottom - v.getMeasuredHeight(); 210 v.layout(left, top, right, bottom); 211 if (v.isFocusable()) { 212 toFocus = v; 213 break; 214 } 215 } 216 } 217 if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) { 218 while (mFirstPosition + rv.getChildCount() < rv.getAdapter().getItemCount() && 219 newViewsHeight < mScrollDistance) { 220 View v = recycler.getViewForPosition(mFirstPosition + rv.getChildCount()); 221 final int top = rv.getChildAt(rv.getChildCount() - 1).getBottom(); 222 addView(v); 223 v.measure(MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 224 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 225 final int bottom = top + v.getMeasuredHeight(); 226 v.layout(left, top, right, bottom); 227 if (v.isFocusable()) { 228 toFocus = v; 229 break; 230 } 231 } 232 } 233 234 return toFocus; 235 } 236 237 public void detachAndScrapViewsOutOfBounds(RecyclerView.Recycler recycler) { 238 final RecyclerView parent = getRecyclerView(); 239 final int childCount = parent.getChildCount(); 240 final int parentWidth = parent.getWidth(); 241 final int parentHeight = parent.getHeight(); 242 boolean foundFirst = false; 243 int first = 0; 244 int last = 0; 245 for (int i = 0; i < childCount; i++) { 246 final View v = parent.getChildAt(i); 247 if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth && 248 v.getBottom() >= 0 && v.getTop() <= parentHeight)) { 249 if (!foundFirst) { 250 first = i; 251 foundFirst = true; 252 } 253 last = i; 254 } 255 } 256 for (int i = childCount - 1; i > last; i--) { 257 recycler.detachAndScrapView(parent.getChildAt(i)); 258 } 259 for (int i = 0; i < first; i++) { 260 recycler.detachAndScrapView(parent.getChildAt(i)); 261 } 262 if (parent.getChildCount() == 0) { 263 mFirstPosition = 0; 264 } else { 265 mFirstPosition += first; 266 } 267 } 268 } 269 270 class MyAdapter extends RecyclerView.Adapter { 271 private int mBackground; 272 private ArrayList<String> mValues; 273 274 public MyAdapter(String[] strings) { 275 TypedValue val = new TypedValue(); 276 RecyclerViewActivity.this.getTheme().resolveAttribute( 277 R.attr.selectableItemBackground, val, true); 278 mBackground = val.resourceId; 279 mValues = new ArrayList(); 280 Collections.addAll(mValues, strings); 281 } 282 283 @Override 284 public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType) { 285 final ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this)); 286 h.textView.setMinimumHeight(128); 287 h.textView.setFocusable(true); 288 h.textView.setBackgroundResource(mBackground); 289 h.textView.setOnClickListener(new View.OnClickListener() { 290 @Override 291 public void onClick(View v) { 292 final int pos = h.getPosition(); 293 if (mValues.size() > pos + 1) { 294 final String t = mValues.get(pos); 295 mValues.set(pos, mValues.get(pos + 1)); 296 mValues.set(pos + 1, t); 297 notifyDataSetChanged(); 298 } 299 } 300 }); 301 return h; 302 } 303 304 @Override 305 public void bindViewHolder(RecyclerView.ViewHolder holder, int position) { 306 ((ViewHolder) holder).textView.setText(mValues.get(position)); 307 } 308 309 @Override 310 public int getItemCount() { 311 return mValues.size(); 312 } 313 } 314 315 static class ViewHolder extends RecyclerView.ViewHolder { 316 public TextView textView; 317 318 public ViewHolder(TextView v) { 319 super(v); 320 textView = v; 321 } 322 } 323} 324