应用背景中的视差效果

9

我是Android世界的新手。我想在我的应用程序中添加一些视差背景效果。

我该如何做?在Android中如何实现这个功能?

有没有更有效的方法来创建2-3层视差背景?是否有一些工具或类在Android API中可以使用?

或者也许我必须在代码中“手动”修改背景图片位置或边距?

我正在使用API级别19。

我尝试了解Paralloid库,但这太难理解了,没有任何解释。我是Android和Java的新手,不熟悉所有布局和其他UI对象,但我熟悉MVC。

我开始使用bounty,也许有人可以逐步解释该库的工作原理。


也许可以看看这个?https://github.com/chrisjenx/Paralloid - Drew
我不明白它是如何工作的 :( 大量代码,零注释。 - Kamil
Kamil,我已经修改了我的答案! - Drew
另外,你具体不理解哪段代码? - Drew
你应该添加更多细节和更好的解释,说明你想要实现什么(例如:我有一个viewpager/list/scroller/等等...)。如果你对sdk没有太多信心,只需尝试从用户角度进行解释。一个好答案的关键是一个详细的问题。 - a.bertucci
显示剩余2条评论
7个回答

13

以下是您可以做的:

在您的activity/fragment布局文件中指定2个ScrollView(例如background_sv和content_sv)。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.parallax.MyScrollView
        android:id="@+id/background_sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

            <ImageView
                android:id="@+id/parallax_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="..." />
    </com.example.parallax.MyScrollView>

    <com.example.parallax.MyScrollView
        android:id="@+id/content_sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </com.example.parallax.MyScrollView>

</RelativeLayout>

在内容滚动视图中添加一个与背景高度相同且透明的虚拟视图。现在,将一个滚动监听器附加到内容滚动视图上。当内容滚动视图被滚动时,调用

mBgScrollView.scrollTo(0, (int)(y /*scroll Of content_sv*/ / 2f));

现有的API不支持获取滚动事件。因此,我们需要创建一个自定义ScrollView来提供ScrollViewListener。

package com.example.parallax;

// imports;

public class MyScrollView extends ScrollView {

    public interface ScrollViewListener {
        void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy);
    }

    private ScrollViewListener scrollViewListener = null;

    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if(scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }
}

这里是同时承载内容的ScrollView和背景的ScrollView的活动

package com.example.parallax;

// imports;

public class ParallaxActivity extends Activity implements ScrollViewListener {

    private MyScrollView mBgScrollView;
    private MyScrollView mContentScrollView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        mBgScrollView = findViewById(R.id.background_sv);
        mContentScrollView = findViewById(R.id.content_sv);
        mContentScrollView.setOnScrollListener(this);
    }

    // this is method for onScrollListener put values according to your need
    @Override
    public void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy) {
        super.onScrollChanged(scrollView, x, y, oldx, oldy);
        // when the content scrollview will scroll by say 100px, 
        // the background scrollview will scroll by 50px. It will 
        // look like a parallax effect where the background is 
        // scrolling with a different speed then the content scrollview.
        mBgScrollView.scrollTo(0, (int)(y / 2f));
    }
}

看起来简单干净,但是... ScrollView 中没有 onScroll 事件。 - Kamil
好的。我认为这是最接近简单且可行的视差效果,但它并不完整。在我奖励您赏金之前,请添加如何添加onScrollChanged事件的信息。这个答案有效(Andy的回答):https://dev59.com/xm865IYBdhLWcg3wLbar - Kamil
是的,我们需要根据onScrollChanged事件获取滚动值。需要创建CustomScrollView,就像您指定的链接中所做的那样。 - Sagar Waghmare
3
你能否编辑你的回答并添加一些代码?我不想因为“去那里找答案”而奖励一个不完整的回答。 - Kamil
已添加所有必要的数据以实现此视差效果。 - Sagar Waghmare
@SagarWaghmare,你忘了实现ScrollViewListener方法了...Kamil已经多次告诉你添加不完整的代码了。 - Himanshu Mori

4
我认为这个问题不够清晰,所以这并不是一个答案,而是一种尝试通过更详细的解释来澄清问题,这些解释无法在评论中包含。

我的问题是关于您想要实现何种视差效果。鉴于以下三个示例(它们是您可以从Play Store安装的演示应用程序),如果有的话,哪一个具有您想要的视差效果?请在评论中回答。

给出答案后,我们都将更容易地提供帮助。如果您编辑您的问题以包含此信息,它将得到改进。


2
以下内容包含了Paralloid的作者发布的一个应用程序示例:

https://github.com/chrisjenx/Paralloid/tree/master/paralloidexample

从GitHub页面的“入门”部分:

Layout

ScrollView

This is an example, please refer to the paralloidexample App for full code.

<FrameLayout ..>
<FrameLayout
        android:id="@+id/top_content"
            android:layout_width="match_parent"
            android:layout_height="192dp"/>

<uk.co.chrisjenx.paralloid.views.ParallaxScrollView
        android:id="@+id/scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">

    <LinearLayout
        android:id="@+id/scroll_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingTop="192dp"/>

</uk.co.chrisjenx.paralloid.views.ParallaxScrollView>
</FrameLayout>

Fragment

Inside your onViewCreated() or onCreateView().

//...
FrameLayout topContent = (FrameLayout) rootView.findViewById(R.id.top_content);
ScrollView scrollView = (ScrollView) rootView.findViewById(R.id.scroll_view);
if (scrollView instanceof Parallaxor) {
        ((Parallaxor) scrollView).parallaxViewBy(topContent, 0.5f);
}
// TODO: add content to top/scroll content

Thats it!

Have a look at the Parallaxor interface for applicable Parallax methods.

希望这有所帮助!
另外,这里 是谷歌 Android 入门页面的链接。
还有,这里 是一个针对完全初学者的 Java 教程的链接。
以及 这里 是一些关于布局的文档,它们“定义用户界面的视觉结构”。
话虽如此,您将使用布局来定义界面的外观,并使用随后的示例代码来定义交互时发生的情况。
附言:您可以在 这里 看到应用程序的运行情况。

而且,如果您查看了文档仍有疑问,也许我们应该从那里开始! - Drew

1

以下是使用ScrollView及其背景图像实现此操作的方法。我已经将代码提交到github

您需要扩展ScrollViewDrawable类。

  1. 默认情况下,ScrollView背景高度与视口高度相同。为了实现视差效果,背景高度应该更大,并且应该基于ScrollView子高度和我们想要施加的背景滚动因子。 背景滚动系数为1表示,背景高度与ScrollView子高度相同,因此背景将随着子内容的滚动而滚动。 0.5表示,背景高度为ScrollView子扩展高度的0.5倍,将比子内容滚动慢50%。这有效地带来了视差滚动效果。

从ScrollView构造函数中调用以下方法:

void init() {
    // Calculate background drawable size before first draw of scrollview
    getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            // Remove the listener
            getViewTreeObserver().removeOnPreDrawListener(this);

            mDrawable = (ParallaxDrawable) getBackground();

            if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
                // Get the only child of scrollview
                View child = getChildAt(0);
                int width = child.getWidth();
                // calculate height of background based on child height and scroll factor
                int height = (int) (getHeight() + (child.getHeight() - getHeight()) * mScrollFactor);
                mDrawable.setSize(width, height);
            }       

            return true;
        }       
    });     
}
  1. 当ScrollView滚动时,在绘制背景时考虑滚动偏移量。这基本上实现了视差效果。

ParallaxScrollView:

视差滚动视图:
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
   if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
       // set the scroll offset for the background drawable.
       mDrawable.setScrollOffset(x*mScrollFactor, y*mScrollFactor);
   }
} 

视差可绘制对象:

@Override
public void draw(Canvas canvas) {
    // To move the background up, translate canvas by negative offset
    canvas.translate(-mScrollXOffset, -mScrollYOffset);
    mDrawable.draw(canvas);
    canvas.translate(mScrollXOffset, mScrollYOffset);
}


protected void onBoundsChange(Rect bounds) {
    // This sets the size of background drawable.
    mDrawable.setBounds(new Rect(bounds.top, bounds.left, bounds.left + mWidth, bounds.top + mHeight));
}

ParallaxScrollView和ParallaxDrawable的使用:

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.parallax_layout);

        final ParallaxScrollView scrollView = (ParallaxScrollView) findViewById(R.id.sv);
        ParallaxDrawable drawable = new ParallaxDrawable(getResources().getDrawable(R.drawable.bg));
        scrollView.setBackground( drawable, 0.2f );
    }
}

parallax_layout.xml:

<manish.com.parallax.ParallaxScrollView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/sv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

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

            <TextView
                android:id="@+id/tv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#fff"
                android:text="@string/text" />

            <View
                android:layout_width="match_parent"
                android:layout_height="5dp" />
            ...
        </LinearLayout>
</manish.com.parallax.ParallaxScrollView>

1
我使用ParallaxScroll库。非常易于使用,有良好的示例和完善的文档。

好的,我不知道还有另一个免费库。我会看一下,但是我觉得你不会因为两个句子而得到500加分 :) - Kamil
这是一个非常好用的库,如果它对您有所帮助,我会非常感激您授予奖金 :) - Mark Buikema
@Kamil,我在想如果这个答案有效,谁将获得赏金呢? :3 - berserk
@berserk 很难选择。我在结合了两个答案的信息之后制作了视差效果... - Kamil

0

Android API并没有提供太多具体的工具,这一点你可能已经注意到了。在API 20中,他们添加了高程属性,这是一个深度属性。这本身并不支持视差布局,但我认为这是谷歌为了让这种工作更容易而迈出的一步。如果你想猜测一下何时会加入视差效果,我会说在API 25发布之前,基于最新的更新和电池效率的进展,很有可能会加入视差效果。

现在,你所需要做的就是监听某种移动,并根据表示高程的值来改变视图的位置。

你的问题促使我升级了自己的项目,这就是我使用Fragment内的ViewDragHelper实现它的方式。

public class MainFragment extends Fragment implements View.OnTouchListener {
    private ImageView mDecor, mBamboo, mBackgroundBamboo;
    private RelativeLayout mRootLayout;
    private ViewDragHelper mDragHelper;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
        mRootLayout = (RelativeLayout) inflater.inflate(R.layout.fragment_main, container, false);
        mRootLayout.setOnTouchListener(this);
        mDecor = (ImageView) mRootLayout.findViewById(R.id.decor);
        mBamboo = (ImageView) mRootLayout.findViewById(R.id.bamboo);
        mBackgroundBamboo = (ImageView) mRootLayout.findViewById(R.id.backround_bamboo);

        mDragHelper = ViewDragHelper.create(mRootLayout, 1.0f, new ViewDragHelper.Callback() {
            private final float MAX_LEFT = -0;
            private final float MAX_TOP = -20;
            private final float MAX_RIGHT = 50;
            private final float MAX_BOTTOM = 10;

            private final float MULTIPLIER = 0.1f;

            private final int DECOR_ELEVATION = 3;
            private final int FRONT_BAMBOO_ELEVATION = 6;
            private final int BACKGROUND_BAMBOO_ELEVATION = 1;

            private float mLeft = 0;
            private float mTop = 0;

            @Override
            public boolean tryCaptureView(View view, int i) {
                return true;
            }

            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                mTop += dy * MULTIPLIER;
                mTop = mTop > MAX_BOTTOM ? MAX_BOTTOM : mTop < MAX_TOP ? MAX_TOP : mTop;

                mDecor.setTranslationY(mTop * DECOR_ELEVATION);
                mBamboo.setTranslationY(mTop * FRONT_BAMBOO_ELEVATION);
                mBackgroundBamboo.setTranslationY(mTop * BACKGROUND_BAMBOO_ELEVATION);
                return 0;
            }

            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                mLeft += dx * MULTIPLIER;
                mLeft = mLeft < MAX_LEFT ? MAX_LEFT : mLeft > MAX_RIGHT ? MAX_RIGHT : mLeft;

                mDecor.setTranslationX(mLeft * DECOR_ELEVATION);
                mBamboo.setTranslationX(mLeft * FRONT_BAMBOO_ELEVATION);
                mBackgroundBamboo.setTranslationX(mLeft * BACKGROUND_BAMBOO_ELEVATION);
                return 0;
            }

            @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){
                mRootLayout.requestLayout();
            }
        });
        return mRootLayout;
    }



    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        mDragHelper.processTouchEvent(motionEvent);
        // you can still use this touch listener for buttons etc.
        return true;
    }
}

0

你好,你可以使用以下代码来使用ParallaxView类

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;

public class ParallaxView extends SurfaceView implements Runnable {

    private volatile boolean running;
    private Thread gameThread = null;

    // For drawing
    private Paint paint;
    private Canvas canvas;
    private SurfaceHolder ourHolder;

    // Holds a reference to the Activity
    Context context;

    // Control the fps
    long fps =60;

    // Screen resolution
    int screenWidth;
    int screenHeight;

    ParallaxView(Context context, int screenWidth, int screenHeight) {
        super(context);

        this.context = context;

        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;

        // Initialize our drawing objects
        ourHolder = getHolder();
        paint = new Paint();

    }

    @Override
    public void run() {

        while (running) {
            long startFrameTime = System.currentTimeMillis();

            update();

            draw();

            // Calculate the fps this frame
            long timeThisFrame = System.currentTimeMillis() - startFrameTime;
            if (timeThisFrame >= 1) {
                fps = 1000 / timeThisFrame;
            }
        }
    }

    private void update() {
        // Update all the background positions

    }

    private void draw() {

        if (ourHolder.getSurface().isValid()) {
            //First we lock the area of memory we will be drawing to
            canvas = ourHolder.lockCanvas();

            //draw a background color
            canvas.drawColor(Color.argb(255, 0, 3, 70));

            // Draw the background parallax

            // Draw the rest of the game
            paint.setTextSize(60);
            paint.setColor(Color.argb(255, 255, 255, 255));
            canvas.drawText("I am a plane", 350, screenHeight / 100 * 5, paint);
            paint.setTextSize(220);
            canvas.drawText("I'm a train", 50, screenHeight / 100*80, paint);

            // Draw the foreground parallax

            // Unlock and draw the scene
            ourHolder.unlockCanvasAndPost(canvas);
        }
    }

    // Clean up our thread if the game is stopped
    public void pause() {
        running = false;
        try {
            gameThread.join();
        } catch (InterruptedException e) {
            // Error
        }
    }

    // Make a new thread and start it
    // Execution moves to our run method
    public void resume() {
        running = true;
        gameThread = new Thread(this);
        gameThread.start();
    }

}// End of ParallaxView

想要了解更多,您可以前往 **

**: http://gamecodeschool.com/android/coding-a-parallax-scrolling-background-for-android/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接