1 /* 2 * Copyright 2018 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 package com.android.pump.fragment; 18 19 import android.content.Context; 20 import android.graphics.Rect; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.util.DisplayMetrics; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.ImageView; 28 import android.widget.TextView; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.UiThread; 33 import androidx.core.view.ViewCompat; 34 import androidx.fragment.app.Fragment; 35 import androidx.recyclerview.widget.GridLayoutManager; 36 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; 37 import androidx.recyclerview.widget.RecyclerView; 38 import androidx.recyclerview.widget.SimpleItemAnimator; 39 40 import com.android.pump.R; 41 import com.android.pump.activity.MovieDetailsActivity; 42 import com.android.pump.db.MediaDb; 43 import com.android.pump.db.Movie; 44 import com.android.pump.util.Globals; 45 46 import java.util.List; 47 48 @UiThread 49 public class MovieFragment extends Fragment { 50 private RecyclerView mRecyclerView; 51 newInstance()52 public static @NonNull Fragment newInstance() { 53 return new MovieFragment(); 54 } 55 56 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)57 public @NonNull View onCreateView(@NonNull LayoutInflater inflater, 58 @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 59 View view = inflater.inflate(R.layout.fragment_movie, container, false); 60 mRecyclerView = view.findViewById(R.id.fragment_movie_recycler_view); 61 mRecyclerView.setHasFixedSize(true); 62 mRecyclerView.setAdapter(new MovieAdapter(requireContext())); 63 mRecyclerView.addItemDecoration(new SpaceItemDecoration(4, 16)); 64 65 GridLayoutManager gridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); 66 gridLayoutManager.setSpanSizeLookup( 67 new HeaderSpanSizeLookup(gridLayoutManager.getSpanCount())); 68 69 ((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false); 70 71 // TODO(b/123707260) Enable view caching 72 //mRecyclerView.setItemViewCacheSize(0); 73 //mRecyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); 74 return view; 75 } 76 77 private static class MovieAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 78 implements MediaDb.UpdateCallback { 79 private final MediaDb mMediaDb; 80 private final List<Movie> mMovies; // TODO(b/123710968) Use android.support.v7.util.SortedList/android.support.v7.widget.util.SortedListAdapterCallback instead 81 MovieAdapter(@onNull Context context)82 private MovieAdapter(@NonNull Context context) { 83 setHasStableIds(true); 84 mMediaDb = Globals.getMediaDb(context); 85 mMovies = mMediaDb.getMovies(); 86 } 87 onAttachedToRecyclerView(@onNull RecyclerView recyclerView)88 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { 89 mMediaDb.addMovieUpdateCallback(this); 90 } 91 onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)92 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { 93 mMediaDb.removeMovieUpdateCallback(this); 94 } 95 96 @Override onCreateViewHolder( @onNull ViewGroup parent, int viewType)97 public @NonNull RecyclerView.ViewHolder onCreateViewHolder( 98 @NonNull ViewGroup parent, int viewType) { 99 if (viewType == R.layout.header) { 100 return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()) 101 .inflate(viewType, parent, false)) { }; 102 } else { 103 return new MovieViewHolder(LayoutInflater.from(parent.getContext()) 104 .inflate(viewType, parent, false)); 105 } 106 } 107 108 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)109 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 110 if (position == 0) { 111 // TODO Handle header view 112 } else { 113 Movie movie = mMovies.get(position - 1); 114 mMediaDb.loadData(movie); // TODO Where should we call this? In bind()? 115 ((MovieViewHolder) holder).bind(movie); 116 } 117 } 118 119 @Override getItemCount()120 public int getItemCount() { 121 return mMovies.size() + 1; 122 } 123 124 @Override getItemId(int position)125 public long getItemId(int position) { 126 return position == 0 ? -1 : mMovies.get(position - 1).getId(); 127 } 128 129 @Override getItemViewType(int position)130 public int getItemViewType(int position) { 131 return position == 0 ? R.layout.header : R.layout.movie; 132 } 133 134 @Override onItemsInserted(int index, int count)135 public void onItemsInserted(int index, int count) { 136 notifyItemRangeInserted(index + 1, count); 137 } 138 139 @Override onItemsUpdated(int index, int count)140 public void onItemsUpdated(int index, int count) { 141 notifyItemRangeChanged(index + 1, count); 142 } 143 144 @Override onItemsRemoved(int index, int count)145 public void onItemsRemoved(int index, int count) { 146 notifyItemRangeRemoved(index + 1, count); 147 } 148 } 149 150 private static class MovieViewHolder extends RecyclerView.ViewHolder { MovieViewHolder(@onNull View itemView)151 private MovieViewHolder(@NonNull View itemView) { 152 super(itemView); 153 } 154 bind(@onNull Movie movie)155 private void bind(@NonNull Movie movie) { 156 ImageView imageView = itemView.findViewById(R.id.movie_image); 157 TextView textView = itemView.findViewById(R.id.movie_text); 158 159 Uri posterUri = movie.getPosterUri(); 160 if (posterUri == null) { 161 posterUri = movie.getThumbnailUri(); 162 } 163 imageView.setImageURI(posterUri); 164 textView.setText(movie.getTitle()); 165 166 itemView.setOnClickListener((view) -> 167 MovieDetailsActivity.start(view.getContext(), movie)); 168 } 169 } 170 171 private static class SpaceItemDecoration extends RecyclerView.ItemDecoration { 172 private final int mXOffset; 173 private final int mYOffset; 174 SpaceItemDecoration(int xOffset, int yOffset)175 private SpaceItemDecoration(int xOffset, int yOffset) { 176 mXOffset = xOffset; 177 mYOffset = yOffset; 178 } 179 180 @Override getItemOffsets(@onNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state)181 public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 182 DisplayMetrics displayMetrics = new DisplayMetrics(); 183 ViewCompat.getDisplay(parent).getMetrics(displayMetrics); 184 outRect.left = outRect.right = (int) Math.ceil(mXOffset * displayMetrics.density); 185 if (parent.getChildAdapterPosition(view) > 0) { 186 outRect.bottom = (int) Math.ceil(mYOffset * displayMetrics.density); 187 } 188 } 189 } 190 191 private static class HeaderSpanSizeLookup extends SpanSizeLookup { 192 private final int mSpanCount; 193 HeaderSpanSizeLookup(int spanCount)194 private HeaderSpanSizeLookup(int spanCount) { 195 mSpanCount = spanCount; 196 } 197 198 @Override getSpanSize(int position)199 public int getSpanSize(int position) { 200 return position == 0 ? mSpanCount : 1; 201 } 202 203 @Override getSpanIndex(int position, int spanCount)204 public int getSpanIndex(int position, int spanCount) { 205 return position == 0 ? 0 : (position - 1) % spanCount; 206 } 207 208 @Override getSpanGroupIndex(int adapterPosition, int spanCount)209 public int getSpanGroupIndex(int adapterPosition, int spanCount) { 210 return adapterPosition == 0 ? 0 : ((adapterPosition - 1) / spanCount) + 1; 211 } 212 } 213 } 214