How to have a wider image scrolling in the background

后端 未结 2 1965
既然无缘
既然无缘 2021-01-31 11:49

Like in LinkedIn the first three screens

  1. Splash
  2. Login/Signup Buttons
  3. Login/Signup Form

all ha

相关标签:
2条回答
  • 2021-01-31 12:33

    One way to do it is to extend ViewPager. It's already been done by someone and you can check the code on github.

    0 讨论(0)
  • 2021-01-31 12:52

    This is called parallax scroll, and I implemented it using 2 layers: One for the content and another one for the background. The content, you place it on a ViewPager without background. Note that instead of activities you will be using Fragments (every page will be a fragment) that will be animated by the viewpager. (See FragmentStatePagerAdapter)

    The background goes on a background layer, obviously behind viewpager and independent from it. It can be an image inside a scrollview, or a image whose clipping region you'll be moving, or a image that you render via drawBitmap(x,y). Please see attached source for my solution, that extends a View whose background can be scrolled just calling a method "setPercent"

    Then you override

    viewPager.setOnPageChangeListener(new OnPageChangeListener(){
    
        @Override
        public void onPageScrolled(int position, float percent, int pixoffset) {
    
            // this is called while user's flinging with:
            // position is the page number
            // percent is the percentage scrolled (0...1)
            // pixoffset is the pixel offset related to that percentage
            // so we got everything we need ....
    
            int totalpages=mViewPagerAdapter.getCount(); // the total number of pages
            float finalPercentage=((position+percent)*100/totalpages); // percentage of this page+offset respect the total pages
            setBackgroundX ((int)finalPercentage);
        }
    }
    
    void setBackgroundX(int scrollPosition) {
            // now you have to scroll the background layer to this position. You can either adjust the clipping or
            // the background X coordinate, or a scroll position if you use an image inside an scrollview ...
                        // I personally like to extend View and draw a scaled bitmap with a clipping region (drawBitmap with Rect parameters), so just modifying the X position then calling invalidate will do. See attached source ParallaxBackground
              parallaxBackground.setPercent(position);
    }
    

    And now the parallax background view, that goes behind the ViewPager. I post here a full, working version of my own ParallaxBackgroundView. This is actually tested code.

            package com.regaliz.gui.views;
    
        import android.content.Context;
        import android.graphics.Bitmap;
        import android.graphics.Bitmap.Config;
        import android.graphics.Canvas;
        import android.graphics.Paint;
        import android.graphics.Rect;
        import android.graphics.drawable.BitmapDrawable;
        import android.graphics.drawable.Drawable;
        import android.util.AttributeSet;
        import android.util.Log;
        import android.view.View;
    
        /**
         * Implements a horizontal parallax background. The image is set via setImageDrawable(), it is then scaled to 150% and 
         * you set the percentage via setPErcentage.
         * @author rodo
         */
    
        public class ParallaxBackground extends View {
    
            private final static String TAG="ParallaxBackground";
            private final static int MODE_PRESCALE=0, MODE_POSTSCALE=1;
    
            /** How much a image will be scaled  */
            /** Warning: A full screen image on a Samsung 10.1 scaled to 1.5 consumes 6Mb !! So be careful */
            private final static float FACTOR=1.5f;
    
            /** The current background */
            private Bitmap mCurrentBackground=null;
    
            /** Current progress 0...100 */
            private float mOffsetPercent=0;
    
            /** Flag to activate */
            private boolean isParallax=true;
    
            /** The parallax mode (MODE_XXX) */
            private int mParallaxMode=MODE_PRESCALE;
    
            /** precalc stuff to tighten onDraw calls */
            private int  mCurrentFactorWidth;
            private float mCurrentFactorMultiplier;
            private Rect mRectDestination, mRectSource;
    
            private Paint mPaint;
    
    
            public ParallaxBackground(Context context, AttributeSet attrs) {
                super(context, attrs);
                construct(context);
            }
    
            public ParallaxBackground(Context context) {
                super(context);
                construct(context);
            }
    
            /**
             * Enables or disables parallax mode
             * @param status
             */
    
            public void setParallax(boolean status) {
                Log.d(TAG, "*** PARALLAX: "+status);
                isParallax=status;
            }
    
            /**
             * Sets the parallax memory mode. MODE_PRESCALE uses more memory but scrolls slightly smoother. MODE_POSTSCALE uses less memory but is more CPU-intensive.
             * @param mode
             */
    
            public void setParallaxMemoryMode(int mode) {
                mParallaxMode=mode;
                if (mCurrentBackground!=null) {
                    mCurrentBackground.recycle();
                    mCurrentBackground=null;
                }
            }
    
            /**
             * Seth the percentage of the parallax scroll. 0 Means totally left, 100 means totally right.
             * @param percentage The perc,
             */
    
            public void setPercent(float percentage) {
                if (percentage==mOffsetPercent) return;
                if (percentage>100) percentage=100;
                if (percentage<0) percentage=0;
                mOffsetPercent=percentage; 
                invalidate();
            }
    
            /**
             * Wether PArallax is active or not.
             * @return ditto.
             */
    
            public boolean isParallax() {
                return isParallax && (mCurrentBackground!=null);
            }
    
            /**
             * We override setBackgroundDrawable so we can set the background image as usual, like in a normal view.
             * If parallax is active, it will create the scaled bitmap that we use on onDraw(). If parallax is not
             * active, it will divert to super.setBackgroundDrawable() to draw the background normally.
             * If it is called with anything than a BitMapDrawable, it will clear the stored background and call super()
             */
    
            @Override
            public void setBackgroundDrawable (Drawable d) {
    
                Log.d(TAG,  "*** Set background has been called !!");
    
                if ((!isParallax) || (!(d instanceof BitmapDrawable))) {
                    Log.d(TAG, "No parallax is active: Setting background normally.");
                    if (mCurrentBackground!=null) {
                        mCurrentBackground.recycle(); // arguably here
                        mCurrentBackground=null;
                    }
                    super.setBackgroundDrawable(d);
                    return;
                }
    
                switch (mParallaxMode) {
    
                case MODE_POSTSCALE:
                    setBackgroundDrawable_postscale(d);
                    break;
    
                case MODE_PRESCALE:
                    setBackgroundDrawable_prescale(d);
                    break;
                }
    
            }
    
            private void setBackgroundDrawable_prescale(Drawable incomingImage) {
    
                Bitmap original=((BitmapDrawable) incomingImage).getBitmap();
                Log.v(TAG, "Created bitmap for background : original: "+original.getByteCount()+", w="+original.getWidth()+", h="+original.getHeight());
    
                mCurrentBackground=Bitmap.createBitmap((int) (this.getWidth()*FACTOR), this.getHeight(), Config.ARGB_8888);
                Canvas canvas=new Canvas(mCurrentBackground);
    
                // we crop the original image up and down, as it has been expanded to FACTOR
                // you can play with the Adjustement value to crop top, center or bottom.
                // I only use center so its hardcoded.
    
                float scaledBitmapFinalHeight=original.getHeight()*mCurrentBackground.getWidth()/original.getWidth();
                int adjustment=0;
    
                if (scaledBitmapFinalHeight>mCurrentBackground.getHeight()) {
                    // as expected, we have to crop up&down to maintain aspect ratio
                    adjustment=(int)(scaledBitmapFinalHeight-mCurrentBackground.getHeight()) / 4;
                } 
    
                Rect srect=new Rect(0,adjustment,original.getWidth(), original.getHeight()-adjustment);
                Rect drect=new Rect(0,0,mCurrentBackground.getWidth(), mCurrentBackground.getHeight());
    
                canvas.drawBitmap(original, srect, drect, mPaint);
    
                Log.v(TAG, "Created bitmap for background : Size: "+mCurrentBackground.getByteCount()+", w="+mCurrentBackground.getWidth()+", h="+mCurrentBackground.getHeight());
    
                // precalc factor multiplier
                mCurrentFactorMultiplier=(FACTOR-1)*getWidth()/100;
    
                original.recycle();
                System.gc();
    
                invalidate();
            }
    
    
    
            private void setBackgroundDrawable_postscale (Drawable d) {
    
                mCurrentBackground=((BitmapDrawable) d).getBitmap();
    
                int currentBackgroundWidth=mCurrentBackground.getWidth(),
                    currentBackgroundHeight=mCurrentBackground.getHeight(),
                    currentFactorHeight=(int) (currentBackgroundHeight/FACTOR);
    
                mCurrentFactorWidth=(int) (currentBackgroundWidth/FACTOR);
                mCurrentFactorMultiplier=(FACTOR-1)*currentBackgroundWidth/100;
                mRectDestination=new Rect(0,0,getWidth(), getHeight());
                mRectSource=new Rect(0,0,mCurrentFactorWidth,currentFactorHeight);
                invalidate();
            }
    
            @Override
            public void onDraw(Canvas canvas) {
                if ((isParallax) && (mCurrentBackground!=null)) {
                    if (mParallaxMode==MODE_POSTSCALE) onDraw_postscale(canvas); else onDraw_prescale(canvas);
                } else super.onDraw(canvas);
            }
    
            private void onDraw_prescale(Canvas canvas) {
                int oxb=(int) (mCurrentFactorMultiplier*mOffsetPercent);
                canvas.drawBitmap(mCurrentBackground, -oxb, 0, mPaint);
            }
    
            private void onDraw_postscale(Canvas canvas) {
                int oxb=(int) (mCurrentFactorMultiplier*mOffsetPercent);
                mRectSource.left=oxb;
                mRectSource.right=mCurrentFactorWidth+oxb;
                canvas.drawBitmap(mCurrentBackground,mRectSource,mRectDestination, mPaint);
            }
    
            private void construct(Context context) {
                mPaint=new Paint();
            }
        }
    
        //// EOF ParallaxBackground.java
    

    Note: You can instantiate the ParallaxBackground either programmatically or in the XML. Just make sure it is behind the viewpager. To instance it in an XML you don't need to do special things:

    <com.regaliz.gui.views.ParallaxBackground
        android:id="@+id/masterBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
    

    Then you can use the component just like any other view

    ParallaxBackground back=findViewById(R.id.masterBackground);
    back.setBackgroundDrawable(R.drawable.your_cool_drawable);
    

    Note 2: If you are using Jelly Bean API, you'll see that SetBackgroundDrawable(Drawable d) has been substituted by setBackground(Drawable d) . I don't use JB api as of yet, but all you have to do is to rename setBackgroundDrawable to setBackground. ** This is important **

    Note 3: The ParallaxBackgroundView has 2 modes : MODE_PRESCALE and MODE_POSTSCALE. Mode PRESCALE scales a bitmap and keeps it always in memory, so onDraw should be faster. Mode POSTSCALE does not do any prescaling, instead, the scaling is done on onDraw(). This is pretty slower, but it may be of use for low-memory devices that can't afford to hold a huge bitmap in memory.

    Hope it helps!

    By the way I'm always interested in optimizing my code, so if somebody has a great suggestion, specially performance or memory related, or enhances this class please post it !!!

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