Rox Teddy
Rox Teddy

Reputation: 101

Android : Restoring recyclerview parallax

I try to code my own parallax effect using a recycler view. It's works pretty well at the moment... until I don't change my device orientation.

At state restoration, I get back my "metrics" variable I use to calculate the header Y translation, no problem with that. But I have troubles getting height from my different views after restoration.

basically here's what I log :

D/parallax﹕ gridHeight: 0 - toolbar: 0 - poster: 0

I tried using .measure() then getMeasuredHeight() but here's what I get :

D/parallax﹕ gridHeight: 0 - toolbar: 128 - poster: 1108

Maybe I missused Measure. Or maybe I should use my parallax method at another moment ? (clother to the runtime ?) If you got any clue...

This is my first question here after a year of reading. Hope I did this the good way. And thank you for any help ;)

Here's my code :

public class ParallaxActivity extends Activity {

    private int                     metrics = 0;
    private int                     resize = 0;
    private int                     gridHeight = -1;
    private int                     toolbarHeight = -1;
    private int                     posterHeight = -1;
    private boolean                 docked = false;

    private Toolbar                 toolbar;
    private ImageView               poster;
    private LinearLayout            header;
    private RecyclerView            grid;
    private RecyclerView.LayoutManager  manager;

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

    setContentView(R.layout.activity_parallax2);

    toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
    toolbar.setNavigationIcon(R.drawable.ic_launcher);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    });

    poster = (ImageView)findViewById(R.id.poster);
    grid = (RecyclerView)findViewById(R.id.list);
    header = (LinearLayout)findViewById(R.id.header);

    manager = new GridLayoutManager(this, 2);
    grid.setLayoutManager(manager);
    DefaultItemAnimator animator = new DefaultItemAnimator();
    grid.setItemAnimator(animator);
    RecyclerGridAdapter ad = new RecyclerGridAdapter(this);
    grid.setAdapter(ad);
    ad.update();

    grid.setOnScrollListener(new RecyclerView.OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            metrics += dy / 3;
            parallax();
        }
    });
}

@Override
protected void onSaveInstanceState(Bundle b)
{
    super.onSaveInstanceState(b);
    b.putInt("metrics", metrics);
}

@Override
protected void onRestoreInstanceState(Bundle b)
{
    super.onRestoreInstanceState(b);
    metrics = b.getInt("metrics", 0);
}

@Override
public void onAttachedToWindow()
{
    if (metrics != 0)
        parallax();
}

private void parallax()
{
    if (gridHeight == -1)
    {
        gridHeight = grid.getHeight();
        toolbarHeight = toolbar.getHeight();
        posterHeight = poster.getHeight();
        Log.d("parallax", "gridHeight: " + gridHeight + " - toolbar: " + toolbarHeight + " - poster: " + posterHeight);
    }
    if (!docked && metrics > posterHeight - toolbarHeight)
    {
        docked = true;
        toolbar.setBackgroundColor(0xff000000);
    }
    if (docked && metrics < posterHeight - toolbarHeight)
    {
        docked = false;
        toolbar.setBackgroundColor(0x00000000);
    }

    if (metrics < 0)
        resize = 0;
    else if ( metrics > posterHeight - toolbarHeight)
        resize = posterHeight - toolbarHeight;
    else
        resize = metrics;

    header.setTranslationY(-resize);
    grid.setTranslationY(-resize);
    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) grid.getLayoutParams();
    params.height = gridHeight + resize;
    grid.setLayoutParams(params);
}
}

and my layout :

<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar_actionbar"
    android:background="@null"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:elevation="5dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<LinearLayout
    android:id="@+id/header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:elevation="0dp">

        <ImageView
            android:id="@+id/poster"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scaleType="centerCrop"
            android:adjustViewBounds="true"
            android:src="@drawable/jpeg"/>
        <TextView
            android:id="@+id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="SerieKids"
            android:textSize="20dp"
            android:textColor="#ffffff"
            android:background="#000000"
            android:paddingStart="100dp"/>
</LinearLayout>

<android.support.v7.widget.RecyclerView
    android:id="@+id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"/>
</LinearLayout>
</RelativeLayout>

And finally the way I use measure()

if (gridHeight == -1)
{
    toolbar.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    poster.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    grid.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

    gridHeight = grid.getMeasuredHeight();
    toolbarHeight = toolbar.getMeasuredHeight();
    posterHeight = poster.getMeasuredHeight();
    Log.d("parallax", "gridHeight: " + gridHeight + " - toolbar: " + toolbarHeight + " - poster: " + posterHeight);
}

Upvotes: 1

Views: 1642

Answers (1)

Pedro Oliveira
Pedro Oliveira

Reputation: 20500

Add a onGlobalLayoutListener to your RecyclerView. That way you're making sure your parallax routine is only called after you view has been completly drawn. Also make sure you unregister the globalLayout otherwise it will be called every time you draw your view (if you're using it to control the rotation changes then there is no need to remove the listener I think. That way it will always be called when you rotate because the layout will be drawn again).

An example of how to register and unregister the layout listener:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                       view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } else {
                       view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                   //your parallax routine here
                }
            });

Upvotes: 1

Related Questions