Странное поведение при прокрутке StaggeredGridLayout

У меня есть RecyclerView для отображения некоторых изображений, загруженных Picasso. Поскольку у них разное соотношение сторон, я использую StaggeredGridLayoutManager. Все выглядит хорошо, но при прокрутке и появлении нового изображения изображения немного перемещаются вверх или вниз, как будто их перемещают. Они не остаются на одном месте.

Дело в том, что если я использую GridLayout, все работает отлично, похоже, что разные размеры элементов все портят.

Это класс ActivityMain:

protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        // Especificamos el layout 'products_grid.xml'
        setContentView(R.layout.products_grid);

        _initData();
        _initAuxViews();
        _initToolbar();
        _initNavigationDrawers();
        _initAnimations();

        new ConnectToServer().execute();
    }



protected void _initRecyclerView()
{   
    mProductsRecyclerView = (RecyclerView)findViewById(R.id.grid_recycler);
    mStaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    mProductAdapter = new ProductsGridAdapter(this, mProductsDisplayedList);

    mProductsRecyclerView.setLayoutManager(mStaggeredGridLayoutManager);
    mProductsRecyclerView.setAdapter(mProductAdapter);
    mProductsRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener()
            {
               ...
            }
}

main.xml, у меня кастомный RecyclerView, но это не влияет, проверил.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.widget.DrawerLayout
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/shop_background_bw">

        <!-- Contenido principal -->
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <!-- Recylcer Grid -->
            <com.wallakoala.wallakoala.Views.GridRecyclerView
                android:id="@+id/grid_recycler"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scrollbars="none"
                android:clipToPadding="false"
                android:paddingTop="?attr/actionBarSize"
                android:layoutAnimation="@anim/grid_layout_animation"/>

            <!-- Toolbar -->
            <include android:id="@+id/appbar"
                layout="@layout/toolbar">

            </include>

            <!-- Loading View -->
            <com.wang.avi.AVLoadingIndicatorView
                android:id="@+id/avloadingIndicatorView"
                android:layout_width="125dp"
                android:layout_height="125dp"
                android:layout_gravity="center"
                android:visibility="gone"
                app:indicator="BallClipRotate"
                app:indicator_color="@color/colorAccent"/>

            <!-- Texto de no prductos -->
            <TextView
                android:id="@+id/nodata_textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/action_bar_height"
                android:layout_gravity="center_horizontal"
                android:visibility="gone"
                android:paddingTop="@dimen/action_bar_height"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="@string/nodata_message"
                android:textSize="20sp"
                android:textColor="@color/colorText"/>

        </FrameLayout>

        <include layout="@layout/left_navigation_drawer"/>

    </android.support.v4.widget.DrawerLayout>

</android.support.design.widget.CoordinatorLayout>

item_grid.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/card_item"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:cardPreventCornerOverlap="false"
    app:cardElevation="4dp"
    app:cardCornerRadius="4dp"
    app:cardUseCompatPadding="true">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Loading View -->
        <com.wang.avi.AVLoadingIndicatorView
            android:id="@+id/avloadingitem"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_gravity="center"
            android:visibility="gone"
            app:indicator="BallClipRotate"
            app:indicator_color="@color/colorAccent"/>

        <!-- Background-->
        <com.makeramen.roundedimageview.RoundedImageView
            android:id="@+id/grid_background"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="@color/colorText"
            android:alpha="0.2"
            android:visibility="gone"
            app:riv_corner_radius_bottom_left="4dp"
            app:riv_corner_radius_bottom_right="4dp"
            app:riv_corner_radius_top_left="4dp"
            app:riv_corner_radius_top_right="4dp"/>

        <!-- Main Image-->
        <com.makeramen.roundedimageview.RoundedImageView
            android:id="@+id/grid_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            app:riv_corner_radius_bottom_left="4dp"
            app:riv_corner_radius_bottom_right="4dp"
            app:riv_corner_radius_top_left="4dp"
            app:riv_corner_radius_top_right="4dp"/>

        <!-- Footer -->
        <RelativeLayout
            android:id="@+id/footer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:alpha="0.75">

            <!-- Info extra -->
            <include android:id="@+id/extraInfo"
                layout="@layout/product_footer_extra"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="visible"/>

            <!-- Info principal -->
            <include android:id="@+id/mainFooter"
                layout="@layout/product_footer"
                android:layout_height="@dimen/footer_height"
                android:layout_width="match_parent"
                android:layout_below="@id/extraInfo"/>

        </RelativeLayout>

    </FrameLayout>

</android.support.v7.widget.CardView>

РЕДАКТИРОВАТЬ: Код адаптера:

public class ProductsGridAdapter extends RecyclerView.Adapter<ProductsGridAdapter.ProductHolder>
{
    /* Constants */
    private static final String TAG = "CUOKA";
    private static final String PACKAGE = "com.wallakoala.wallakoala";

    /* Context */
    private static Context mContext;

    /* Data */
    private static List<Product> mProductList;

    public static class ProductHolder extends RecyclerView.ViewHolder implements View.OnClickListener
    {
        private Product mProduct;

        private ImageButton mFavImageButton;
        private ImageView mProductImageView;
        private ImageView mErrorImageView;
        private View mLoadingView;
        private View mBackgroundView;
        private View mProductFooterView, mProductFooterExtraView, mProductFooterMainView;
        private TextView mTitleTextView, mSubtitleTextView, mNameTextView, mPriceTextView;

        private Animation scaleUpFooterExtra, scaleDownFooterExtra;

        public ProductHolder(View itemView)
        {
            super(itemView);

            mErrorImageView   = (ImageView)itemView.findViewById(R.id.broken_image);
            mTitleTextView    = (TextView)itemView.findViewById(R.id.footer_title);
            mSubtitleTextView = (TextView)itemView.findViewById(R.id.footer_subtitle);
            mProductImageView = (ImageView)itemView.findViewById(R.id.grid_image);
            mFavImageButton   = (ImageButton)itemView.findViewById(R.id.footer_fav_button);
            mNameTextView     = (TextView)itemView.findViewById(R.id.name);
            mPriceTextView    = (TextView)itemView.findViewById(R.id.price);

            mBackgroundView         = itemView.findViewById(R.id.grid_background);
            mLoadingView            = itemView.findViewById(R.id.avloadingitem);
            mProductFooterView      = itemView.findViewById(R.id.footer);
            mProductFooterExtraView = itemView.findViewById(R.id.extraInfo);
            mProductFooterMainView  = itemView.findViewById(R.id.mainFooter);

            mProductFooterView.setOnClickListener(this);
            //mProductImageView.setOnClickListener(this);

            scaleUpFooterExtra = AnimationUtils.loadAnimation(mContext, R.anim.scale_up);
            scaleDownFooterExtra = AnimationUtils.loadAnimation(mContext, R.anim.scale_down);
        }

        public void bindProduct(Product product)
        {
            /* Inicializamos los TextViews */
            mTitleTextView.setText(product.getShop());
            mSubtitleTextView.setText(product.getColors().get(0).getReference());
            mNameTextView.setText(product.getName());
            mPriceTextView.setText(String.format("%.2f", product.getPrice()) + "€");

            /* Ocultamos la info, IMPORTANTE. Cosas malas pasan si no se pone. Tambien la imagen de error. */
            mProductFooterExtraView.setVisibility(View.GONE);
            mProductFooterMainView.setVisibility(View.GONE);
            mErrorImageView.setVisibility(View.GONE);

            /* Mostramos la view de carga y el background */
            mLoadingView.setVisibility(View.VISIBLE);

            mBackgroundView.setVisibility(View.VISIBLE);

            /* Ponemos el icono del corazon. */
            mFavImageButton.setBackgroundResource(R.drawable.ic_favorite_border_white);

            /* Cargamos la imagen usando Picasso */
            String url = product.getColors().get(0).getImages().get(0).getPath().replaceAll(".jpg", "_Small.jpg");
            Picasso.with(mContext)
                   .load(url)
                   .into(mProductImageView, new Callback() {
                       @Override
                       public void onSuccess() {
                           mBackgroundView.setVisibility(View.GONE);
                           mLoadingView.setVisibility(View.GONE);
                           mProductFooterMainView.setVisibility(View.VISIBLE);
                       }

                       @Override
                       public void onError() {
                           mLoadingView.setVisibility(View.GONE);
                           mErrorImageView.setVisibility(View.VISIBLE);
                       }
                   });

            mProduct = product;
        }

    public ProductsGridAdapter(Context context, List<Product> productList)
    {
        mContext = context;
        mProductList = productList;
    }

    public void updateProductList(List<Product> productList)
    {
        mProductList = productList;
    }

    @Override
    public ProductHolder onCreateViewHolder(ViewGroup viewGroup, int viewType)
    {
        View itemView = LayoutInflater.from(viewGroup.getContext())
                                      .inflate(R.layout.product_item_grid
                                                    , viewGroup
                                                    , false );

        return new ProductHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final ProductHolder productHolder, int pos)
    {
        productHolder.bindProduct(mProductList.get(pos));
    }

    @Override
    public int getItemCount()
    {
        return mProductList.size();
    }
}

EDIT 2: я выяснил, что происходит. Кардвью сначала создается без изображения, при загрузке макет перерисовывается, поэтому получается странное движение. Я пытался сделать это:

КОГДА изображение загружается впервые, я сохраняю его высоту, а когда recyclerView прокручивается вверх, я устанавливаю фоновое изображение карты с уже сохраненной высотой, чтобы оно имело те же размеры, что и изображение, и это должно делать обманывать. Но это не так. Это то, что я пробовал, этот код принадлежит конструктору держателя в адаптере.

        final ViewTreeObserver mProductImageViewTreeObserver = mProductImageView.getViewTreeObserver();
        final ViewTreeObserver mBackgroundTreeObserver = mBackgroundView.getViewTreeObserver();
        mProductImageViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                if (mProductHeight != mProductImageView.getHeight())
                {
                    mProductHeight = mProductImageView.getHeight();
                }
            }
        });

        mBackgroundTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
        {
            @Override
            public boolean onPreDraw()
            {
                if((mProductHeight > 0) && (mProductHeight != mBackgroundView.getHeight()))
                {
                    mBackgroundView.getLayoutParams().height = mProductHeight;
                }

                return true;
            }
        });

Кто-нибудь знает, что не так?

Заранее спасибо,


person cuoka    schedule 27.01.2016    source источник
comment
Не могли бы вы опубликовать свой ProductsGridAdapter?   -  person Evin1_    schedule 28.01.2016
comment
Вопрос отредактирован с кодом   -  person cuoka    schedule 28.01.2016
comment
Я заметил, что проблема в том, что изображение не загружается, Android рисует карточку с другими видами. Как только изображение загружается, оно снова рисуется, поэтому создается движение. Пожалуйста, смотрите редактирование для возможного подхода   -  person cuoka    schedule 28.01.2016


Ответы (1)


Я сам нашел решение. Хитрость заключается в том, чтобы сохранить соотношение сторон при первой загрузке изображения в целевой объект.

Затем в OnPrepareLoad проверьте, загружалось ли это изображение ранее. Если это так, используйте сохраненное соотношение сторон, чтобы установить окончательную высоту, таким образом, нет никакой перенастройки, и прокрутка работает плавно.

mTarget = new Target()
        {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from)
            {
                mLoadingView.setVisibility(View.GONE);
                mProductFooterMainView.setVisibility(View.VISIBLE);

                mProductImageView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                mProductImageView.setBackgroundColor(-1);
                mProductImageView.setAlpha(1.0f);


                if (mProductBitmapArray[position] == 0.0f)
                    mProductBitmapArray[position] = (double)bitmap.getHeight() / (double)bitmap.getWidth();

                mProductImageView.setImageBitmap(bitmap);
            }

            @Override
            public void onBitmapFailed(Drawable errorDrawable)
            {
                ... do stuff
            }

            @Override
            public void onPrepareLoad(Drawable placeHolderDrawable)
            {
                mProductImageView.setImageBitmap(null);

                if (mProductBitmapArray[position] != 0)
                    mProductImageView.getLayoutParams().height = (int)(mProductImageView.getWidth()
                                                                            * mProductBitmapArray[position]);
                else
                    mProductImageView.getLayoutParams().height = 600;

                mProductImageView.setBackgroundColor(mContext.getResources().getColor(R.color.colorText));
                mProductImageView.setAlpha(0.1f);
            }
        };
person cuoka    schedule 27.01.2016