RecyclerViewActivity.java revision 7b1352adb9f9384cb7c5b0995bbe45992c00f4f3
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 boolean structureChanged) { 88 final int parentBottom = getHeight() - getPaddingBottom(); 89 90 final View oldTopView = getChildCount() > 0 ? getChildAt(0) : null; 91 int oldTop = getPaddingTop(); 92 if (oldTopView != null) { 93 oldTop = oldTopView.getTop(); 94 } 95 96 detachAndScrapAttachedViews(recycler); 97 98 int top = oldTop; 99 int bottom; 100 final int left = getPaddingLeft(); 101 final int right = getWidth() - getPaddingRight(); 102 103 final int count = adapter.getItemCount(); 104 for (int i = 0; mFirstPosition + i < count && top < parentBottom; i++, top = bottom) { 105 View v = recycler.getViewForPosition(adapter, mFirstPosition + i); 106 addView(v, i); 107 measureChildWithMargins(v, 0, 0); 108 bottom = top + v.getMeasuredHeight(); 109 v.layout(left, top, right, bottom); 110 } 111 112 removeAndRecycleScrap(recycler); 113 } 114 115 @Override 116 public RecyclerView.LayoutParams generateDefaultLayoutParams() { 117 return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 118 ViewGroup.LayoutParams.WRAP_CONTENT); 119 } 120 121 @Override 122 public boolean canScrollVertically() { 123 return true; 124 } 125 126 @Override 127 public int scrollVerticallyBy(int dy, RecyclerView.Adapter adapter, 128 RecyclerView.Recycler recycler) { 129 if (getChildCount() == 0) { 130 return 0; 131 } 132 133 int scrolled = 0; 134 final int left = getPaddingLeft(); 135 final int right = getWidth() - getPaddingRight(); 136 if (dy < 0) { 137 while (scrolled > dy) { 138 final View topView = getChildAt(0); 139 final int hangingTop = Math.max(-topView.getTop(), 0); 140 final int scrollBy = Math.min(scrolled - dy, hangingTop); 141 scrolled -= scrollBy; 142 offsetChildrenVertical(scrollBy); 143 if (mFirstPosition > 0 && scrolled > dy) { 144 mFirstPosition--; 145 View v = recycler.getViewForPosition(adapter, mFirstPosition); 146 addView(v, 0); 147 measureChildWithMargins(v, 0, 0); 148 final int bottom = topView.getTop(); // TODO decorated top? 149 final int top = bottom - v.getMeasuredHeight(); 150 v.layout(left, top, right, bottom); 151 } else { 152 break; 153 } 154 } 155 } else if (dy > 0) { 156 final int parentHeight = getHeight(); 157 while (scrolled < dy) { 158 final View bottomView = getChildAt(getChildCount() - 1); 159 final int hangingBottom = Math.max(bottomView.getBottom() - parentHeight, 0); 160 final int scrollBy = -Math.min(dy - scrolled, hangingBottom); 161 scrolled -= scrollBy; 162 offsetChildrenVertical(scrollBy); 163 if (scrolled < dy && getItemCount() > mFirstPosition + getChildCount()) { 164 View v = recycler.getViewForPosition(adapter, 165 mFirstPosition + getChildCount()); 166 final int top = getChildAt(getChildCount() - 1).getBottom(); 167 addView(v); 168 measureChildWithMargins(v, 0, 0); 169 final int bottom = top + v.getMeasuredHeight(); 170 v.layout(left, top, right, bottom); 171 } else { 172 break; 173 } 174 } 175 } 176 recycleViewsOutOfBounds(recycler); 177 return scrolled; 178 } 179 180 @Override 181 public View onFocusSearchFailed(View focused, int direction, 182 RecyclerView.Adapter adapter, RecyclerView.Recycler recycler) { 183 final int oldCount = getChildCount(); 184 185 if (oldCount == 0) { 186 return null; 187 } 188 189 final int left = getPaddingLeft(); 190 final int right = getWidth() - getPaddingRight(); 191 192 View toFocus = null; 193 int newViewsHeight = 0; 194 if (direction == View.FOCUS_UP || direction == View.FOCUS_BACKWARD) { 195 while (mFirstPosition > 0 && newViewsHeight < mScrollDistance) { 196 mFirstPosition--; 197 View v = recycler.getViewForPosition(adapter, mFirstPosition); 198 final int bottom = getChildAt(0).getTop(); // TODO decorated top? 199 addView(v, 0); 200 measureChildWithMargins(v, 0, 0); 201 final int top = bottom - v.getMeasuredHeight(); 202 v.layout(left, top, right, bottom); 203 if (v.isFocusable()) { 204 toFocus = v; 205 break; 206 } 207 } 208 } 209 if (direction == View.FOCUS_DOWN || direction == View.FOCUS_FORWARD) { 210 while (mFirstPosition + getChildCount() < getItemCount() && 211 newViewsHeight < mScrollDistance) { 212 View v = recycler.getViewForPosition(adapter, mFirstPosition + getChildCount()); 213 final int top = getChildAt(getChildCount() - 1).getBottom(); 214 addView(v); 215 measureChildWithMargins(v, 0, 0); 216 final int bottom = top + v.getMeasuredHeight(); 217 v.layout(left, top, right, bottom); 218 if (v.isFocusable()) { 219 toFocus = v; 220 break; 221 } 222 } 223 } 224 225 return toFocus; 226 } 227 228 public void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) { 229 final int childCount = getChildCount(); 230 final int parentWidth = getWidth(); 231 final int parentHeight = getHeight(); 232 boolean foundFirst = false; 233 int first = 0; 234 int last = 0; 235 for (int i = 0; i < childCount; i++) { 236 final View v = getChildAt(i); 237 if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth && 238 v.getBottom() >= 0 && v.getTop() <= parentHeight)) { 239 if (!foundFirst) { 240 first = i; 241 foundFirst = true; 242 } 243 last = i; 244 } 245 } 246 for (int i = childCount - 1; i > last; i--) { 247 removeAndRecycleViewAt(i, recycler); 248 } 249 for (int i = first - 1; i >= 0; i--) { 250 removeAndRecycleViewAt(i, recycler); 251 } 252 if (getChildCount() == 0) { 253 mFirstPosition = 0; 254 } else { 255 mFirstPosition += first; 256 } 257 } 258 } 259 260 class MyAdapter extends RecyclerView.Adapter<ViewHolder> { 261 private int mBackground; 262 private ArrayList<String> mValues; 263 264 public MyAdapter(String[] strings) { 265 TypedValue val = new TypedValue(); 266 RecyclerViewActivity.this.getTheme().resolveAttribute( 267 R.attr.selectableItemBackground, val, true); 268 mBackground = val.resourceId; 269 mValues = new ArrayList<String>(); 270 Collections.addAll(mValues, strings); 271 } 272 273 @Override 274 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 275 final ViewHolder h = new ViewHolder(new TextView(RecyclerViewActivity.this)); 276 h.textView.setMinimumHeight(128); 277 h.textView.setFocusable(true); 278 h.textView.setBackgroundResource(mBackground); 279 h.textView.setOnClickListener(new View.OnClickListener() { 280 @Override 281 public void onClick(View v) { 282 final int pos = h.getPosition(); 283 if (mValues.size() > pos + 1) { 284 final String t = mValues.get(pos); 285 mValues.set(pos, mValues.get(pos + 1)); 286 mValues.set(pos + 1, t); 287 notifyItemRemoved(pos); 288 notifyItemInserted(pos + 1); 289 } 290 } 291 }); 292 return h; 293 } 294 295 @Override 296 public void onBindViewHolder(ViewHolder holder, int position) { 297 holder.textView.setText(mValues.get(position)); 298 } 299 300 @Override 301 public int getItemCount() { 302 return mValues.size(); 303 } 304 } 305 306 static class ViewHolder extends RecyclerView.ViewHolder { 307 public TextView textView; 308 309 public ViewHolder(TextView v) { 310 super(v); 311 textView = v; 312 } 313 314 @Override 315 public String toString() { 316 return super.toString() + " '" + textView.getText(); 317 } 318 } 319} 320