1package com.example.android.leanback; 2 3import static com.example.android.leanback.CardPresenter.CONTENT; 4import static com.example.android.leanback.CardPresenter.IMAGE; 5import static com.example.android.leanback.CardPresenter.TITLE; 6 7import android.content.Context; 8import android.content.Intent; 9import android.os.Bundle; 10import android.os.Handler; 11import android.text.TextUtils; 12import android.util.Log; 13 14import androidx.annotation.Nullable; 15import androidx.core.app.ActivityOptionsCompat; 16import androidx.core.content.res.ResourcesCompat; 17import androidx.leanback.widget.ArrayObjectAdapter; 18import androidx.leanback.widget.DiffCallback; 19import androidx.leanback.widget.HeaderItem; 20import androidx.leanback.widget.ImageCardView; 21import androidx.leanback.widget.ListRow; 22import androidx.leanback.widget.ListRowPresenter; 23import androidx.leanback.widget.ObjectAdapter; 24import androidx.leanback.widget.OnItemViewClickedListener; 25import androidx.leanback.widget.Presenter; 26import androidx.leanback.widget.Row; 27import androidx.leanback.widget.RowPresenter; 28 29import java.util.ArrayList; 30 31public class SearchFragment extends androidx.leanback.app.SearchFragment 32 implements androidx.leanback.app.SearchFragment.SearchResultProvider { 33 private static final String TAG = "leanback.SearchFragment"; 34 private static final int NUM_ROWS = 3; 35 private static final int SEARCH_DELAY_MS = 1000; 36 37 private ArrayObjectAdapter mRowsAdapter; 38 private Handler mHandler = new Handler(); 39 private String mQuery; 40 41 // Flag to represent if data set one is presented in the fragment 42 private boolean mIsDataSetOnePresented; 43 44 // Adapter for first row 45 private ArrayObjectAdapter mFirstRowAdapter; 46 47 // The diff callback which defines the standard to judge if two items are the same or if 48 // two items have the same content. 49 private DiffCallback<PhotoItem> mDiffCallback = new DiffCallback<PhotoItem>() { 50 51 // when two photo items have the same id, they are the same from adapter's 52 // perspective 53 @Override 54 public boolean areItemsTheSame(PhotoItem oldItem, PhotoItem newItem) { 55 return oldItem.getId() == newItem.getId(); 56 } 57 58 // when two photo items is equal to each other (based on the equal method defined in 59 // PhotoItem), they have the same content. 60 @Override 61 public boolean areContentsTheSame(PhotoItem oldItem, PhotoItem newItem) { 62 return oldItem.equals(newItem); 63 } 64 65 @Nullable 66 @Override 67 public Object getChangePayload(PhotoItem oldItem, PhotoItem newItem) { 68 Bundle diff = new Bundle(); 69 if (oldItem.getImageResourceId() 70 != newItem.getImageResourceId()) { 71 diff.putLong(IMAGE, newItem.getImageResourceId()); 72 } 73 74 if (oldItem.getTitle() != null && newItem.getTitle() != null 75 && !oldItem.getTitle().equals(newItem.getTitle())) { 76 diff.putString(TITLE, newItem.getTitle()); 77 } 78 79 if (oldItem.getContent() != null && newItem.getContent() != null 80 && !oldItem.getContent().equals(newItem.getContent())) { 81 diff.putString(CONTENT, newItem.getContent()); 82 } 83 return diff; 84 } 85 }; 86 87 @Override 88 public void onCreate(Bundle savedInstanceState) { 89 super.onCreate(savedInstanceState); 90 91 mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); 92 93 final Context context = getActivity(); 94 setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), 95 R.drawable.ic_title, context.getTheme())); 96 setTitle("Leanback Sample App"); 97 setSearchResultProvider(this); 98 setOnItemViewClickedListener(new ItemViewClickedListener()); 99 } 100 101 @Override 102 public ObjectAdapter getResultsAdapter() { 103 return mRowsAdapter; 104 } 105 106 @Override 107 public boolean onQueryTextChange(String newQuery) { 108 Log.i(TAG, String.format("Search Query Text Change %s", newQuery)); 109 mRowsAdapter.clear(); 110 loadQuery(newQuery); 111 return true; 112 } 113 114 @Override 115 public boolean onQueryTextSubmit(String query) { 116 Log.i(TAG, String.format("Search Query Text Submit %s", query)); 117 mRowsAdapter.clear(); 118 loadQuery(query); 119 return true; 120 } 121 122 private void loadQuery(String query) { 123 mQuery = query; 124 mHandler.removeCallbacks(mDelayedLoad); 125 if (!TextUtils.isEmpty(query) && !query.equals("nil")) { 126 mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS); 127 } 128 } 129 130 private void loadRows() { 131 HeaderItem header = new HeaderItem(0, mQuery + " results row " + 0); 132 133 // Every time when the query event is fired, we will update the fake search result in the 134 // first row based on the flag mIsDataSetOnePresented flag. 135 // Also the first row adapter will only be created once so the animation will be triggered 136 // when the items in the adapter changed. 137 if (!mIsDataSetOnePresented) { 138 if (mFirstRowAdapter == null) { 139 mFirstRowAdapter = createFirstListRowAdapter(); 140 } else { 141 mFirstRowAdapter.setItems(createDataSetOneDebug(), mDiffCallback); 142 } 143 mIsDataSetOnePresented = true; 144 } else { 145 mFirstRowAdapter.setItems(createDataSetTwoDebug(), mDiffCallback); 146 mIsDataSetOnePresented = false; 147 } 148 mRowsAdapter.add(new ListRow(header, mFirstRowAdapter)); 149 for (int i = 1; i < NUM_ROWS + 1; ++i) { 150 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter()); 151 listRowAdapter.add(new PhotoItem("Hello world", R.drawable.gallery_photo_1)); 152 listRowAdapter.add(new PhotoItem("This is a test", R.drawable.gallery_photo_2)); 153 header = new HeaderItem(i, mQuery + " results row " + i); 154 mRowsAdapter.add(new ListRow(header, listRowAdapter)); 155 } 156 } 157 158 private Runnable mDelayedLoad = new Runnable() { 159 @Override 160 public void run() { 161 loadRows(); 162 } 163 }; 164 165 private final class ItemViewClickedListener implements OnItemViewClickedListener { 166 @Override 167 public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, 168 RowPresenter.ViewHolder rowViewHolder, Row row) { 169 Intent intent = new Intent(getActivity(), DetailsActivity.class); 170 intent.putExtra(DetailsActivity.EXTRA_ITEM, (PhotoItem) item); 171 172 Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation( 173 getActivity(), 174 ((ImageCardView) itemViewHolder.view).getMainImageView(), 175 DetailsActivity.SHARED_ELEMENT_NAME).toBundle(); 176 getActivity().startActivity(intent, bundle); 177 } 178 } 179 180 181 private ArrayObjectAdapter createFirstListRowAdapter() { 182 ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter()); 183 listRowAdapter.setItems(createDataSetOneDebug(), mDiffCallback); 184 mIsDataSetOnePresented = true; 185 return listRowAdapter; 186 } 187 188 /** 189 * Create a data set (data set one) for the last row of this browse fragment. It will be 190 * changed by another set of data when user click one of the photo items in the list. 191 * Different with other rows in the browsing fragment, the photo item in last row all have been 192 * allocated with a unique id. And the id will be used to jduge if two photo items are the same 193 * or not. 194 * 195 * @return List of photoItem 196 */ 197 private ArrayList<PhotoItem> createDataSetOne() { 198 ArrayList<PhotoItem> photoItems = new ArrayList<>(); 199 photoItems.add(new PhotoItem( 200 "Hello world", 201 R.drawable.gallery_photo_1, 202 1)); 203 photoItems.add(new PhotoItem( 204 "This is a test", 205 "Only a test", 206 R.drawable.gallery_photo_2, 207 2)); 208 photoItems.add(new PhotoItem( 209 "Android TV", 210 "by Google", 211 R.drawable.gallery_photo_3, 212 3)); 213 photoItems.add(new PhotoItem( 214 "Leanback", 215 R.drawable.gallery_photo_4, 216 4)); 217 photoItems.add(new PhotoItem( 218 "GuidedStep (Slide left/right)", 219 R.drawable.gallery_photo_5, 220 5)); 221 photoItems.add(new PhotoItem( 222 "GuidedStep (Slide bottom up)", 223 "Open GuidedStepFragment", 224 R.drawable.gallery_photo_6, 225 6)); 226 photoItems.add(new PhotoItem( 227 "Android TV", 228 "open RowsActivity", 229 R.drawable.gallery_photo_7, 230 7)); 231 photoItems.add(new PhotoItem( 232 "Leanback", 233 "open BrowseActivity", 234 R.drawable.gallery_photo_8, 235 8)); 236 photoItems.add(new PhotoItem( 237 "Hello world", 238 R.drawable.gallery_photo_1, 239 1)); 240 photoItems.add(new PhotoItem( 241 "This is a test", 242 "Only a test", 243 R.drawable.gallery_photo_2, 244 2)); 245 photoItems.add(new PhotoItem( 246 "Android TV", 247 "by Google", 248 R.drawable.gallery_photo_3, 249 3)); 250 photoItems.add(new PhotoItem( 251 "Leanback", 252 R.drawable.gallery_photo_4, 253 4)); 254 return photoItems; 255 } 256 257 /** 258 * Create a new data set (data set one) for the last row of this browse fragment. It will be 259 * changed by another set of data when user click one of the photo items in the list. 260 * Different with other rows in the browsing fragment, the photo item in last row all have been 261 * allocated with a unique id. And the id will be used to jduge if two photo items are the same 262 * or not. 263 * 264 * @return List of photoItem 265 */ 266 private ArrayList<PhotoItem> createDataSetTwo() { 267 ArrayList<PhotoItem> photoItems = new ArrayList<>(); 268 photoItems.add(new PhotoItem( 269 "This is a test", 270 "Only a test", 271 R.drawable.gallery_photo_2, 272 2)); 273 photoItems.add(new PhotoItem( 274 "Hello world", 275 R.drawable.gallery_photo_1, 276 1)); 277 photoItems.add(new PhotoItem( 278 "Leanback", 279 R.drawable.gallery_photo_4, 280 4)); 281 photoItems.add(new PhotoItem( 282 "Android TV", 283 "by Google", 284 R.drawable.gallery_photo_3, 285 3)); 286 photoItems.add(new PhotoItem( 287 "change title", 288 R.drawable.gallery_photo_5, 289 5)); 290 photoItems.add(new PhotoItem( 291 "GuidedStep (Slide bottom up)", 292 "change comment", 293 R.drawable.gallery_photo_6, 294 6)); 295 photoItems.add(new PhotoItem( 296 "Android TV", 297 R.drawable.gallery_photo_7, 298 7)); 299 photoItems.add(new PhotoItem( 300 "Leanback", 301 "open BrowseActivity", 302 R.drawable.gallery_photo_7, 303 8)); 304 photoItems.add(new PhotoItem( 305 "Hello world", 306 R.drawable.gallery_photo_1, 307 10)); 308 photoItems.add(new PhotoItem( 309 "This is a test", 310 "Only a test", 311 R.drawable.gallery_photo_2, 312 20)); 313 photoItems.add(new PhotoItem( 314 "Android TV", 315 "by Google", 316 R.drawable.gallery_photo_3, 317 30)); 318 photoItems.add(new PhotoItem( 319 "Leanback", 320 R.drawable.gallery_photo_4, 321 40)); 322 return photoItems; 323 } 324 325 326 private ArrayList<PhotoItem> createDataSetOneDebug() { 327 ArrayList<PhotoItem> photoItems = new ArrayList<>(); 328 photoItems.add(new PhotoItem( 329 "Hello world", 330 R.drawable.gallery_photo_1, 331 1)); 332 return photoItems; 333 } 334 335 /** 336 * Create a new data set (data set one) for the last row of this browse fragment. It will be 337 * changed by another set of data when user click one of the photo items in the list. 338 * Different with other rows in the browsing fragment, the photo item in last row all have been 339 * allocated with a unique id. And the id will be used to jduge if two photo items are the same 340 * or not. 341 * 342 * @return List of photoItem 343 */ 344 private ArrayList<PhotoItem> createDataSetTwoDebug() { 345 ArrayList<PhotoItem> photoItems = new ArrayList<>(); 346 photoItems.add(new PhotoItem( 347 "Hello world Hello world", 348 R.drawable.gallery_photo_1, 349 1)); 350 return photoItems; 351 } 352} 353