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