Spotify ListView header image effect
Asked Answered
P

4

17

The Android version of Spotify has a unique ListView header effect when viewing an artist. Basically the header image appears to maintain it's own scrolling speed apart from the actual list. Does anyone know what I'm talking about? If so, can someone explain how to achieve this effect?

Here is a link to a video outlining the header image effect I'm referring to:

http://servestream.sourceforge.net/20130911_200347.mp4

Penrose answered 29/7, 2013 at 19:42 Comment(2)
Add an image, or upload a video of what you are talking about. Spotify has limited device compatibility.Fabled
Link is no longer availableKucik
D
22

I made an open source library that dose just what you need - https://github.com/nirhart/ParallaxScroll you can see the example here - https://play.google.com/store/apps/details?id=com.nirhart.parallaxscrollexample

Dugger answered 8/3, 2014 at 20:38 Comment(8)
I'm new to android so excuse the ignorance of the question, but how do I add this library to my existing Android Studio project?Uhlan
I'm still using eclipse so I'm not sure, but maybe this post can help you #16588564Dugger
I ended up sticking the individual classes into my project. Thanks.Uhlan
I looked at your src code for this library and there is a slight limitation. ideally it works perfect on a scrollView but a listView it doesnt generate the same effect as the spotify example.Destruct
@NirHartmann First of thanks for library. But How do we use this library. isn't there usage example ?Spendable
@MustafaOlkun the github link include the library and the exampleDugger
@NirHartmann I understood after I downloaded. Your library is simple but It is not contain enough usage example. Maybe you would a short usage example in readme. It can be benefit for everything. Thank you again :)Spendable
Yah this Library have the Fade and Scroll up Parallax effect , But where is the Moving back effect. i didn't saw that one in this Library Like SpotifyCountersignature
F
27

Thanks for posting the video. This is a parallax effect. The following library can help you achieve it:

ParallaxScrollView: A Parallax ScrollView which takes a background and foreground view, in the ParallexScrollView.

Link

So, I went ahead and modified the demo offered on the link. If this is what you are after, let me know and I'll add details on the modifications I did to get this working.

APK Link

How to get this:

If there was anything but a ListView in the scrollable part, the effect would have been easy to achieve. Because the container holding the ListView is an extended ScrollView, things get complicated. The following modifications were made:

In the activity, inflate the following layout:

<couk.jenxsol.parallaxscrollview.views.ParallaxScrollView xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scroll_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DemoActivity" >

<!-- Top Image: Here, the height is set to 300dp. You can set this in code -->
<!-- depending on screen dimensions -->

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:gravity="center"
        android:scaleType="fitXY"
        android:src="@drawable/image_to_use" />

<!-- Foreground -->
<!-- You can place any of the items below as the foreground,  -->
<!-- but for most control, add the scroll view yourself. -->

<!-- This is the area that will hold the ListView -->
<!-- Also note that the LinearLayout's top margin will 
<!-- depend on ImageView's height. Here, there's an overalp of 100dp -->
<!-- between the ImageView and the LinearLayout -->
<!-- Reason: The first TextView(with a transparent background) inside the 
<!-- LinearLayout is displayed over the ImageView. -->
<!-- So, the overlapping height should be equal to first TextView's height -->
<!-- LinearLayout's top margin = ImageView's height - firstTextView's height -->

<!-- AnotherView is an extended LinearLayout that I added to the library -->

    <couk.jenxsol.parallaxscrollview.views.AnotherView
        android:id="@+id/anotherView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:id="@+id/llMainHolder"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="200dp"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/tvTitle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:gravity="center"
                android:padding="@dimen/spacing"
                android:text="Parallax Effect"
                android:textColor="@android:color/white"
                android:textSize="21sp"
                tools:ignore="NewApi" />

            <!-- ListView -->

            <LinearLayout
                android:id="@+id/llMain"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <ListView
                    android:id="@+id/lvMain"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:divider="@android:color/black"
                    android:dividerHeight="2px" >

                </ListView>

            </LinearLayout>            

        </LinearLayout>

    </couk.jenxsol.parallaxscrollview.views.AnotherView>

</couk.jenxsol.parallaxscrollview.views.ParallaxScrollView>

Activity code:

public class DemoActivity extends Activity {

    private ParallaxScrollView mScrollView;
    private ListView lvMain;
    private LinearLayout llMain, llMainHolder;
    private AnotherView anotherView;
    private ImageView iv;
    private TextView tvTitle;

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


        // Inflated layout
        View mContent = getLayoutInflater().inflate(R.layout.activity_demo, null);

        // Initialize components

        mScrollView = (ParallaxScrollView) mContent.findViewById(R.id.scroll_view);

        llMain = (LinearLayout) mContent.findViewById(R.id.llMain);

        llMainHolder = (LinearLayout) mContent.findViewById(R.id.llMainHolder);

        lvMain = (ListView) mContent.findViewById(R.id.lvMain);

        iv = (ImageView) mContent.findViewById(R.id.iv);

        tvTitle = (TextView) mContent.findViewById(R.id.tvTitle);

        anotherView = (AnotherView) mContent.findViewById(R.id.anotherView);

        String[] array = {"one", "two", "three", "four", "five", "six", "seven", "eight",
            "nine", "ten", "evelen", "twelve", "thirteen", "fourteen"};

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.text, array);

        lvMain.setAdapter(adapter);            

        // Set Content
        setContentView(mContent);

        lvMain.post(new Runnable() {

            @Override
            public void run() {

                // Adjusts llMain's height to match ListView's height
                setListViewHeight(lvMain, llMain);  

                // LayoutParams to set the top margin of LinearLayout holding
                // the content. 
                // topMargin = iv.getHeight() - tvTitle.getHeight()
                LinearLayout.LayoutParams p = 
                       (LinearLayout.LayoutParams)llMainHolder.getLayoutParams();
                p.topMargin = iv.getHeight() - tvTitle.getHeight(); 
                llMainHolder.setLayoutParams(p);
            }
        });
    }

    // Sets the ListView holder's height    
    public void setListViewHeight(ListView listView, LinearLayout llMain) {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {

            return;
        }

        int totalHeight = 0;
        int firstHeight = 0;
        int desiredWidth = MeasureSpec.makeMeasureSpec(
                             listView.getWidth(), MeasureSpec.AT_MOST);

        for (int i = 0; i < listAdapter.getCount(); i++) {

            if (i == 0) {
                View listItem = listAdapter.getView(i, null, listView);
                listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
                firstHeight = listItem.getMeasuredHeight();
        }
            totalHeight += firstHeight; 
        }

        LinearLayout.LayoutParams params = 
                         (LinearLayout.LayoutParams)llMain.getLayoutParams();

        params.height = totalHeight + (listView.getDividerHeight() *
                                                (listAdapter.getCount() - 1));
        llMain.setLayoutParams(params);
        anotherView.requestLayout();    
    }
}

The view provided by the library that holds the content (ObservableScrollView) extends a ScrollView. this was causing problems with the ListView that you want to display. I addedAnotherView that extends a LinearLayout instead:

public class AnotherView extends LinearLayout {

private ScrollCallbacks mCallbacks;

    static interface ScrollCallbacks {
        public void onScrollChanged(int l, int t, int oldl, int oldt);
    }

    public void setCallbacks(ScrollCallbacks listener) {
        mCallbacks = listener;
    }

    public AnotherView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
    } 

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mCallbacks != null) {
            mCallbacks.onScrollChanged(l, t, oldl, oldt);
        }
    }

    @Override
    public int computeVerticalScrollRange() {
        return super.computeVerticalScrollRange();
    }              

}

At last: the library provides the parallax effect. The effect in your video is a reverse parallax effect. To get the desired result, a small change is required in ParallaxScrollView.onLayout(). In place of final int scrollYCenterOffset = -mScrollView.getScrollY(), use final int scrollYCenterOffset = mScrollView.getScrollY().

Modified library: Link.

Demo project: Link.

APK (revised / with ListView): Link.

Fabled answered 12/9, 2013 at 5:3 Comment(15)
That's perfect! Could you explain how to achieve that effect in your answer and I will accept it. Thanks again!Penrose
@WilliamSeemann Can you give me an example (a link, html etc.) of what the WebView would show? I haven't had much experience with WebViews. But, I'll give it a go if you can provide a sample link.Fabled
@WilliamSeemann I guess you misunderstood. I was asking what some html in quickText.loadData("some html", "text/html; charset=UTF-8", null); would be.Fabled
@WilliamSeemann I see the problem. Let us continue this discussion in chat.Fabled
I found one issue with this. If you only have just enough list items to allow for scrolling (say 2 or 3) but not enough to completely hide the image header scrolling is very choppy.Penrose
@WilliamSeemann I am not being able to recreate this issue. Scrolling is still smooth with fewer items. However, I found that: the smaller the content height, the faster the image goes off screen. From taking a look at the library's code (ParallaxScrollView.java), I found the following line to be of importance: final int offset = (int) (scrollYCenterOffset * mScrollDiff);. The smaller the offset value, the slower the image will scroll off screen. So, say you want to slow things down, use this: final int offset = (int) (scrollYCenterOffset * mScrollDiff) / 10;.Fabled
@WilliamSeemann If possible, upload the apk file. I'll take a look at it.Fabled
@ user2558882 "I found that: the smaller the content height, the faster the image goes off screen". That's exactly was what I was referring to. Your proposed solution worked perfectly, thanks again!Penrose
How would you make the "Parralax Effect" text act as a header and stick to the top when it reaches? The list would continue scrolling under.Wept
@Wept I haven't tried this myself, but you could have another TextView that starts off with visibility="invisible". This TextView will have to be grouped with the ImageView(id="iv") using a FrameLayout or RelativeLayout. Using ScrollCallbacks, you could check if TextView(with id="tvTitle") has scrolled offscreen. If it has, switch its visibility to invisible and toggle the other TextView's visibility to visible. Needless to say, this will require changes to both the library and your activity/xml. Perhaps, its possible with the library mentioned in the accepted answer?Fabled
From Github and the author, ParallaxScrollView is a dead project now. Updated to Paralloid, link: github.com/chrisjenx/ParalloidMedlar
i tried the new Paralloid but found out that the actual parralex effect only works properly(like the spotify app) when using a scrollViewDestruct
@jonney ... does the above solution work for your listview? I've tried paralloid too and it would not work.Vizcacha
Hey i want to add this solution with this library github.com/dexafree/MaterialList/wiki/WelcomeCard but i cant get it to work any help?Ormiston
@Tony I'll check if its possible next weekend. Sorry, busy till then :((.Fabled
D
22

I made an open source library that dose just what you need - https://github.com/nirhart/ParallaxScroll you can see the example here - https://play.google.com/store/apps/details?id=com.nirhart.parallaxscrollexample

Dugger answered 8/3, 2014 at 20:38 Comment(8)
I'm new to android so excuse the ignorance of the question, but how do I add this library to my existing Android Studio project?Uhlan
I'm still using eclipse so I'm not sure, but maybe this post can help you #16588564Dugger
I ended up sticking the individual classes into my project. Thanks.Uhlan
I looked at your src code for this library and there is a slight limitation. ideally it works perfect on a scrollView but a listView it doesnt generate the same effect as the spotify example.Destruct
@NirHartmann First of thanks for library. But How do we use this library. isn't there usage example ?Spendable
@MustafaOlkun the github link include the library and the exampleDugger
@NirHartmann I understood after I downloaded. Your library is simple but It is not contain enough usage example. Maybe you would a short usage example in readme. It can be benefit for everything. Thank you again :)Spendable
Yah this Library have the Fade and Scroll up Parallax effect , But where is the Moving back effect. i didn't saw that one in this Library Like SpotifyCountersignature
B
0

You could try to use FadingActionBar to replicate the way Google Play Music handles it's artist headers

Bagnio answered 29/7, 2013 at 19:45 Comment(3)
Sorry, I don't want a fading action bar. I just need an effect on the ListView header.Penrose
This answer is correct. The library does what you want including fading the action bar. If you don't want the fade effect, go through the source code, particularly, FadingActionBarHelper.java and use the parts that you need. In particular look at the interactions between the header, the content (in your case the listview), the OnScrollListener, and parts of onNewScroll that update the header height.Aircraftman
This library achieves part of what I want but, as evidenced by your answer, not in a easily reusable or concise fashion. Telling me to read through the source code and chop out the suspected parts that I may not want isn't a straightforward answer, sorry.Penrose
A
0

It is simply done like this, assuming you have a scrollview containing an imageview that you both have references to:

   scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
                    int top = scrollView.getScrollY(); // Increases when scrolling up ^
                    int newTop = (int) (top * .5f);
                    imageFrame.setTranslationY(newTop < 0 ? 0 : newTop);

        }
    });

This will scroll the imageview upwards at half speed compared to the rest of the scrollview, and also checks that it never scrolls down more than it should (past 0)

Arnoldarnoldo answered 22/5, 2015 at 9:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.