java - 为交错 GridView 动态设置固定高度

标签 java android android-recyclerview staggeredgridlayout

我正在尝试使用带有 StaggeredGridLayoutRecyclerView 并通过让它测量 View 并动态设置高度来使其成为固定高度。我覆盖了 onMeasure(),但它似乎并不总是正确测量。我会说它大约有50%的时间有效。另外 50% 的时间它在测量它。我认为这与文本何时包含在 view_tile_small.xml 中有关,但我不确定。

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 博物馆

<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 代码。它在 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

最佳答案

你看过 ViewTreeObserver 吗?

我在通过的项目中遇到了类似的问题,我发现它比 onMesure 更可靠地动态获取布局属性

您可以从这里浏览它:http://developer.android.com/reference/android/view/ViewTreeObserver.html

关于java - 为交错 GridView 动态设置固定高度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33265986/

相关文章:

android - FlatBuffers 可以与 PHP 服务器端一起使用吗?

javascript - React Native 版本不匹配 - Native 版本 : 0. 59.5

android - 为什么我的 Recycler View 选择了错误的项目以及被点击的完全选定的项目?

java - 我怎样才能剪断这根绳子?

java - 由 MouseListener 触发的多个弹跳球

javascript - 添加 cordova.js 时混合移动应用程序崩溃

java - 尝试将消息上传到 Firebase 数据库时出现错误 "found conflicting getters"

java - 在哪里可以找到 org.omg.CosTransactions 包

java - spring mvc Controller 中的无限json序列化

android - 在 Recycler View 中显示来自 firebase 的特定数据