Spotify ListView header image effect

后端 未结 4 388
无人共我
无人共我 2020-12-23 00:24

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 fro

相关标签:
4条回答
  • 2020-12-23 00:46

    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

    0 讨论(0)
  • 2020-12-23 00:46

    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.

    0 讨论(0)
  • 2020-12-23 00:48

    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)

    0 讨论(0)
  • 2020-12-23 00:53

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

    0 讨论(0)
提交回复
热议问题