Dynamically Setting a Fixed Height for a Staggered Grid View

六眼飞鱼酱① 提交于 2019-12-03 09:38:47

问题


I'm trying to take a RecyclerView with a StaggeredGridLayout and make it a fixed height by having it measure the views and set the height dynamically. I'm overriding the onMeasure(), but it does not always seem to measure correctly. I'd say it works about 50% of the time. The other 50% of the time it under measures it. I think it has to do with when the text wraps in the view_tile_small.xml, but I'm not sure.

Fragment

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>

fragment_museum

<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>

ViewTreeObserver code I'm playing with now. It's in Fragment.

    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

回答1:


Have you look at ViewTreeObserver ?

I got a similar problem on a passed project and I have found it more reliable than onMesure to dynamically get Layout properties

You can go through it from here : http://developer.android.com/reference/android/view/ViewTreeObserver.html



来源:https://stackoverflow.com/questions/33265986/dynamically-setting-a-fixed-height-for-a-staggered-grid-view

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!