【问题标题】:How to have a wider image scrolling in the background如何在背景中滚动更宽的图像
【发布时间】:2013-06-20 07:24:18
【问题描述】:

就像在 LinkedIn 的前三个屏幕中一样

  1. 飞溅
  2. 登录/注册按钮
  3. 登录/注册表单

所有都具有相同的图像作为背景,但是当我们从一个活动移动到另一个活动时,背景图像从右向左滚动。

我只能尝试overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); 但这不是它的样子。

【问题讨论】:

  • 所以你想要一个像从左到右的动画滑块?

标签: android android-layout


【解决方案1】:

这称为视差滚动,我使用 2 层实现它:一层用于内容,另一层用于背景。 内容,您将其放置在没有背景的 ViewPager 上。请注意,您将使用由 viewpager 动画的片段(每个页面都是一个片段)而不是活动。 (参见 FragmentStatePagerAdapter)

背景在背景层上,显然在 viewpager 之后并且独立于它。它可以是滚动视图内的图像,也可以是您将要移动其剪辑区域的图像,或者是您通过 drawBitmap(x,y) 渲染的图像。请参阅我的解决方案的附加源代码,它扩展了一个视图,其背景可以滚动,只需调用方法“setPercent”

然后你覆盖

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);
}

现在是视差背景视图,位于 ViewPager 后面。我在这里发布了我自己的 ParallaxBackgroundView 的完整工作版本。这是实际测试过的代码。

        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

注意:您可以通过编程方式或在 XML 中实例化 ParallaxBackground。只要确保它在viewpager后面。要在 XML 中实例化它,您不需要做任何特殊的事情:

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

然后你就可以像使用其他视图一样使用组件了

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

注意 2:如果您使用 Jelly Bean API,您会看到 SetBackgroundDrawable(Drawable d) 已被 setBackground(Drawable d) 替换。我目前还没有使用 JB api,但您所要做的就是将 setBackgroundDrawable 重命名为 setBackground。 ** 这很重要 **

注意 3: ParallaxBackgroundView 有 2 种模式:MODE_PRESCALE 和 MODE_POSTSCALE。模式 PRESCALE 缩放位图并始终将其保存在内存中,因此 onDraw 应该更快。模式 POSTSCALE 不做任何预缩放,而是在 onDraw() 上完成缩放。这相当慢,但它可能适用于无法在内存中保存巨大位图的低内存设备。

希望对您有所帮助!

顺便说一句,我一直对优化我的代码很感兴趣,所以如果有人有很好的建议,特别是性能或内存相关,或者增强这个类,请发表!!!

【讨论】:

  • 如果我使用 viewpager,我无法将它们添加到 backstack..back 按钮行为以及片段转换。我不能在这里使用活动吗?
  • 或者是否可以使用 framelaout 并进行片段替换?这可能会解决我的 backstack 问题。
  • 活动总是有自己的背景,你不能使用背景然后活动在上面。 FrameLayout+Fragment 替换?当然!您也可以使用没有片段的 ViewFlipper。 ViewPager+Fragments 的作用在于,它使设计/添加更多片段/等变得非常容易……此外,您可以在 Google 尝试和测试的组件上插入漂亮的页面转换等。当然,您总是可以自己手动完成。顺便说一句-您也可以捕获 BACK 键并自己处理。如果我是你,我会在使用 ViewPager 时尝试这个,它是一个漂亮的组件。
  • 看看这两个问题,说不定能解决你的问题:stackoverflow.com/questions/12712917/…stackoverflow.com/questions/13185476/…
  • @SwapnilGodambe@ChristianGarcia 这个问题似乎是由返回 0 的 getHeight() 函数引起的。只有当系统实际上有返回高度时,才可以通过在视图上调用 setBackground() 来解决此问题。 stackoverflow.com/questions/4142090/…
【解决方案2】:

一种方法是扩展 ViewPager。已经有人做过了,你可以去github查看代码。

【讨论】:

  • 我同意,这是一个很棒的组件!
猜你喜欢
  • 2021-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多