staggeredgridlayoutmanager recyclerview java android android-recyclerview staggeredgridlayout

java - staggeredgridlayoutmanager - recyclerview android



Configuración dinámica de una altura fija para una vista de cuadrícula escalonada (1)

Estoy tratando de tomar un RecyclerView con un StaggeredGridLayout y convertirlo en una altura fija haciendo que mida las vistas y establezca la altura dinámicamente. Estoy anulando onMeasure() , pero no siempre parece medir correctamente. Yo diría que funciona aproximadamente el 50% del tiempo. El otro 50% del tiempo lo mide. Creo que tiene que ver con cuando el texto se ajusta en view_tile_small.xml , pero no estoy seguro.

Fragmento

public class AtTheMuseumFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener{ //Odds n Ends Variables private MapFragment mapFragment; private FragmentTransaction fragmentTransaction; private User user = new User(); private MuseumCollection museumCollection; private Context context; //Story Grid Adapter Variables private AtTheMuseumAdapter nearMeAdapter; private TileFactory tileFactory; //Interfaces private OnFragmentChangeListener changeListener; @Bind(R.id.stories_list_view) RecyclerView storiesListView; @Bind(R.id.swipe_container) SwipeRefreshLayout swipeRefreshLayout; public static AtTheMuseumFragment newInstance(MuseumCollection museumCollection) { AtTheMuseumFragment fragment = new AtTheMuseumFragment(); Bundle args = new Bundle(); fragment.setArguments(args); return fragment; } public AtTheMuseumFragment() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); museumCollection = ((MainActivity) getActivity()).getMuseumCollection(); context = getActivity().getApplicationContext(); tileFactory = new TileFactory(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_museum, container, false); ButterKnife.bind(this, view); //Sets up the layoutManager to the Mason View storiesListView.setLayoutManager(new MeasuredStaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); //Sets up The map try{ fragmentTransaction = getChildFragmentManager().beginTransaction(); mapFragment = MapFragment.newInstance(MapFragment.MUSEUM); fragmentTransaction.add(R.id.museum_map, mapFragment).commit(); }catch (Exception e){ Log.d("Map - Initial Inflate:", e.getMessage()); } //Sets up the swipe to refresh jawn swipeRefreshLayout.setOnRefreshListener(this); setNearMeAdapter(); return view; } @Override public void onResume(){ super.onResume(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { changeListener = (OnFragmentChangeListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener"); } } @Override public void onDetach() { super.onDetach(); changeListener = null; } /** * Loads the adapter once the data is ready * */ public void setNearMeAdapter(){ List<MuseumObject> newList = museumCollection.getObjectsNearUser(User.position, 4); List<Tile> tiles = tileFactory.createAtMuseumFeed(newList, true); nearMeAdapter = new AtTheMuseumAdapter(context, tiles, getActivity()); storiesListView.setAdapter(nearMeAdapter); } /** * Refreshes Adapter with new data * */ public void refreshNearMeAdapter(){ //TODO CHANGE THIS TO LOCATION BASED WHEN TIME IS RIGHT - Peter //nearMeAdapter.setNewData(MuseumCollection.getObjectsNearUser(User.position, 4)); List<MuseumObject> newList = museumCollection.getRandomOrder(); nearMeAdapter.setNewData(tileFactory.createAtMuseumFeed(newList,false)); } /** * Adds past data to the Adapter * */ public void loadPastObjects(){ //TODO MAKE THIS NOT ONLY LOAD RANDOM DATA - Peter List<MuseumObject> newList = museumCollection.getRandomOrder(); nearMeAdapter.addNewData(tileFactory.createAtMuseumFeed(newList, false)); nearMeAdapter.notifyDataSetChanged(); } @Override public void onRefresh() { user.updateUserLocation(); refreshNearMeAdapter(); mapFragment.refreshMap(museumCollection.getObjectFeed()); swipeRefreshLayout.setRefreshing(false); } public interface OnFragmentChangeListener { void onFragmentChange(String fragment); } @OnClick(R.id.explore_map) public void exploreMap(){ changeListener.onFragmentChange("map"); } @OnClick(R.id.load_more) public void loadMore(){ loadPastObjects(); } }

MeasuredStaggeredGridLayoutManager

public class MeasuredStaggeredGridLayoutManager extends StaggeredGridLayoutManager { public MeasuredStaggeredGridLayoutManager(int spanCount, int orientation) { super(spanCount, orientation); } private int[] mMeasuredDimension = new int[2]; @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; int heightR = 0; int heightL = 0; for (int i = 0; i < getItemCount(); i++) { measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED), mMeasuredDimension); if (getOrientation() == HORIZONTAL) { width = width + mMeasuredDimension[0]; if (i == 0) { height = mMeasuredDimension[1]; } } else { if(i % 2 == 0){ heightL += mMeasuredDimension[1]; }else{ heightR += mMeasuredDimension[1]; } if (i == 0) { width = mMeasuredDimension[0]; } } } switch (widthMode) { case View.MeasureSpec.EXACTLY: width = widthSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } switch (heightMode) { case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.AT_MOST: case View.MeasureSpec.UNSPECIFIED: } if(heightL != 0 || heightR != 0){ height = (heightL > heightR) ? heightL : heightR; } //TODO come up with a better way to fix the slightly wrong height // must be not accounting for padding or margin or something - Peter height += (20 * (getItemCount() / 2)) + 5; setMeasuredDimension(width, height); } private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.getViewForPosition(position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams(); int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width); int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height); view.measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin; measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin; recycler.recycleView(view); } } }

AtTheMuseumAdapter

public class AtTheMuseumAdapter extends RecyclerView.Adapter<AtTheMuseumAdapter.MuseumStoriesViewHolder> { private List<Tile> tiles; private LayoutInflater inflater; private AdapterCallback mListener; private Context context; public AtTheMuseumAdapter(Context context, List<Tile> tiles, Activity activity) { this.tiles = tiles; this.context = context; inflater = LayoutInflater.from(this.context); //Sets up interface between Stock Adapter and Fragment try { this.mListener = ((AdapterCallback) activity); } catch (ClassCastException e) { throw new ClassCastException("Fragment must implement AdapterCallback."); } } @Override public MuseumStoriesViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = inflater.inflate(R.layout.view_tile_small, viewGroup, false); MuseumStoriesViewHolder holder = new MuseumStoriesViewHolder(view); return holder; } @Override public void onBindViewHolder(MuseumStoriesViewHolder holder, int position) { Tile currentTile = tiles.get(position); holder.title.setText(currentTile.getTitle()); holder.desc.setText(currentTile.getDescription()); holder.type.setText(currentTile.getObjectTypeName()); //Using Picasso since it handles caching and all that jazz Picasso.with(context) .load(currentTile.getImg()) .into(holder.img); } @Override public int getItemCount() { return tiles.size(); } public void setNewData(List<Tile> newItems){ tiles = newItems; notifyDataSetChanged(); } public void addNewData(final List<Tile> newItems){ tiles.addAll(newItems); notifyDataSetChanged(); } class MuseumStoriesViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public TextView type,title,desc; public ImageView img; public MuseumStoriesViewHolder(View itemView) { super(itemView); //Tried Butterknife, but it doesn''t seem like it was working in the view holder. - Peter type = (TextView) itemView.findViewById(R.id.small_box_type); title = (TextView) itemView.findViewById(R.id.small_box_title); desc = (TextView) itemView.findViewById(R.id.small_box_desc); img = (ImageView) itemView.findViewById(R.id.small_box_image); itemView.setOnClickListener(this); } @Override public void onClick(View view) { Tile t = tiles.get(getPosition()); switch (t.getObjectTypeName()){ case Tile.OBJECT_NAME: mListener.onObjectClick(t.getObjectID()); break; case Tile.TOUR_NAME: mListener.onTourCLick(t.getTourID()); break; case Tile.STORY_NAME: mListener.onStoryClick(t.getObjectID(), t.getStoryID()); break; } } } public interface AdapterCallback { public void onObjectClick(String objectID); public void onStoryClick(String objectID, String storyID); public void onTourCLick(String tourID); } }

view_tile_small.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/margin_small" android:background="@color/small_box_background_color"> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <!--TODO Make layout_height wrap contenet --> <ImageView android:id="@+id/small_box_image" android:layout_width="match_parent" android:layout_height="150dp" android:adjustViewBounds="true" android:scaleType="centerCrop" android:maxHeight="150dp" android:background="@color/transparent"/> <ImageView android:layout_width="35dp" android:layout_height="35dp" android:layout_gravity="right" android:src="@drawable/abc_btn_rating_star_off_mtrl_alpha" /> <TextView android:id="@+id/small_box_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_normal" android:paddingBottom="@dimen/margin_normal" android:textSize="@dimen/font_small" android:textColor="@color/font_white" android:background="@drawable/small_box_text_bottom_border" android:layout_gravity="bottom" android:text="Object Story" /> </FrameLayout> <TextView android:id="@+id/small_box_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/margin_larger" android:layout_marginBottom="@dimen/margin_normal" android:layout_marginRight="@dimen/margin_larger" android:layout_marginTop="@dimen/margin_larger" android:textSize="@dimen/font_large" android:textColor="@color/font_black" android:text="Sample Text Here" /> <TextView android:id="@+id/small_box_desc" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/margin_larger" android:layout_marginBottom="@dimen/margin_larger" android:textSize="@dimen/font_normal" android:textColor="@color/font_black" android:textStyle="italic" android:text="Sample Text Here" /> </LinearLayout>

fragmento_museo

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context="com.bluecadet.android.nasm.ui.AtTheMuseumFragment" android:id="@+id/swipe_container" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="100dp"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFFFFF" android:descendantFocusability="blocksDescendants" > <TextView android:id="@+id/museum_header" style="@style/header" android:text="@string/museum_header" android:layout_margin="@dimen/margin_larger" android:elevation="8dp" /> <FrameLayout android:layout_width="match_parent" android:layout_height="275dp" android:elevation="2dp" > <FrameLayout android:id="@+id/museum_map" android:layout_height="fill_parent" android:layout_width="match_parent" /> <include layout="@layout/view_explore_map" /> </FrameLayout> <android.support.v7.widget.RecyclerView android:id="@+id/stories_list_view" xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/margin_normal" android:layout_marginLeft="@dimen/margin_small" android:layout_marginRight="@dimen/margin_small" android:layout_marginTop="@dimen/margin_normal" android:stretchMode="columnWidth" /> <Button android:id="@+id/load_more" style="@style/home_button" android:gravity="center" android:text="@string/button_load_more" /> </LinearLayout> </ScrollView> </android.support.v4.widget.SwipeRefreshLayout>

Código de ViewTreeObserver con el que estoy jugando ahora. Está en Fragmento.

mTopStoriesListView.setLayoutManager(new NewMeasuredStaggeredLayoutManager(2, StaggeredGridLayoutManager.VERTICAL, mTopStoriesListView)); mTopStoriesListView.setNestedScrollingEnabled(false); //Testing Issue 54 final ViewTreeObserver viewTreeObserver = mTopStoriesListView.getViewTreeObserver(); if (viewTreeObserver.isAlive()) { viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mTopStoriesListView.getViewTreeObserver().removeOnGlobalLayoutListener(this); int l = 0,r = 0; for(int i = 0 ; i < mNearMeAdapter.getItemCount(); i++){ int h = mTopStoriesListView.getLayoutManager().findViewByPosition(i).getHeight(); ViewGroup.MarginLayoutParams layout = (ViewGroup.MarginLayoutParams) mTopStoriesListView.getLayoutManager() .findViewByPosition(i).getLayoutParams(); int t = layout.topMargin; int b = layout.bottomMargin; if(i % 2 == 0){ l += h + t + b; }else{ r += h + t + b; } } int viewHeight = (l > r) ? l : r; mTopStoriesListView.getLayoutParams().height = viewHeight; Log.d("TAG", String.valueOf(viewHeight)); } }); } //END TEST