Android:在浏览视图时移动背景图像

时间:2011-01-23 18:41:33

标签: android background viewflipper horizontalscrollview

我想要的是背景图片,其行为类似于股票主屏幕或天气应用程序:https://youtube.com/watch?v=i2Oh4GL5wBE#t=0m54s

我只需要一个没有动画的背景图像(就像那个视频中的道路一样),它会在滑动到另一个视图时“滚动一下”。 在我的应用程序中,我想通过滚动背景来浏览一些ListView。

我测试了什么:

  • ViewFlipper:我用大图像将其垂直切成5块。然后我将每个布局(显示ListViews)中的切割图像设置为背景。
  • Horizo​​ntalScrollView:在Horizo​​ntalScrollView中,我添加了一个LinearLayout并将大图像设置为背景。然后我在浏览ListViews时使用GestureDectector添加“捕捉”。

两者都有效,但这需要一个非常大的图像,我不确定它是否可以正确缩放到不同的屏幕尺寸。此外,它的行为与原始主屏幕不同,后台在前景变为下一个视图时会稍微移动。

我在这里阅读了帖子:https://stackoverflow.com/questions/2943619/android-make-application-background-behave-like-homescreen-background

我检查了推荐的Launcher.java,它引导我进入了Workspace.java:

https://android.googlesource.com/platform/packages/apps/Launcher2/+/master/src/com/android/launcher2/Workspace.java

据我所知,它通过设置偏移来使用WallpaperManager。

 mWallpaperManager.setWallpaperOffsets(getWindowToken(),
                 Math.max(0.f, Math.min(mScrollX/(float)scrollRange, 1.f)), 0);

API说:

  

setWallpaperOffsets(的IBinder   windowToken,float xOffset,float   Y偏移)   设置的位置   任何更大的当前壁纸   空间,当壁纸可见时   在给定的窗口后面。

但据我了解,这只会改变手机本身的背景图像。

我该怎么办?您是否知道开源应用程序或示例代码也是如此? 可能我必须自己绘制画布(以前从未做过)。请给我一些建议。

4 个答案:

答案 0 :(得分:10)

最后,我让它工作(仍然需要一些修改)。 :)

我必须承认我无法在ViewGroup本身中绘制背景。嗯,它工作,但我无法控制滚动(滚动太多)。 我基本上做的是合并2个教程。

  • 第一个来自这里:Android Homescreen 它在不同的布局之间切换。首先,我设置了透明的背景。
  • 第二个来自这里:Draggable Symbols 它通过触摸在屏幕上拖动符号(使用onDraw,如建议的那样)。我将符号缩减为单个符号,并将其从ImageView更改为LinearLayout

在该布局中我添加了ViewGroup,在onTouchEventonInterceptTouchEvent中做了一些小的更改,添加了一些不良代码,最后它起作用了。 :)

如果有人感兴趣,我会清理代码并在此发布,但我对我的编码风格感到羞耻,这是一团糟。 ;)

非常感谢,感谢您的帮助! :)

<强>更新 所以,这是代码:

<强> MainActivity.java

package de.android.projects;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

<强> MoveBackground.java

package de.android.projects;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/*
 * LinearLayout which moves the background image by touchEvents. 
 * Taken from: http://eagle.phys.utk.edu/guidry/android/DraggableSymbols.html
 * and changed a little bit
*/

public class MoveBackground extends LinearLayout{

    private Drawable background;     // background picture
    private float X = -300;             // Current x coordinate, upper left corner - 300
    private int scroll;

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

    public MoveBackground(Context context, AttributeSet attrs) {
        super(context);

        background = context.getResources().getDrawable(R.drawable.backgroundpicture);
//just for tests, not really optimized yet :) 
    background.setBounds(0,0,1000,getResources().getDisplayMetrics().heightPixels);

        setWillNotDraw(false);
    }

    /* 
     * Don't need these methods, maybe later for gesture improvements
     */
    /*
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        onTouchEvent(ev);
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();

        switch (action) {
            // MotionEvent class constant signifying a finger-drag event  
            case MotionEvent.ACTION_MOVE: {
                // Request a redraw
                invalidate();
                break;
            }
            // MotionEvent class constant signifying a finger-up event
            case MotionEvent.ACTION_UP:
                invalidate();  // Request redraw
                break;
        }
        return true;
    }
    */


    // This method will be called each time the screen is redrawn. 
    // When to redraw is under Android control, but we can request a redraw 
    // using the method invalidate() inherited from the View superclass.

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

     // get the object movement
        if (BadScrollHelp.getScrollX() != scroll){
            //reduce the scrolling
            X -= scroll / 5;
            scroll = BadScrollHelp.getScrollX();
        }

        // Draw background image at its current locations
        canvas.save();
        canvas.translate(X,0);
        background.draw(canvas);
        canvas.restore();
    }
}

<强> ViewFlipper.java

package de.android.projects;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
import android.widget.Scroller;

/*
 * Flip different views. Taken from tutorial there http://android-projects.de/2011/01/04/android-homescreen-view-flipper/
 */

public class ViewFlipper extends ViewGroup {
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    private int mScrollX = 0;
    private int mCurrentScreen = 0;

    private float mLastMotionX;

    private static final String LOG_TAG = "DragableSpace";

    private static final int SNAP_VELOCITY = 1000;

    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;

    private int mTouchState = TOUCH_STATE_REST;

    private int mTouchSlop = 0;

    public ViewFlipper(Context context) {
        super(context);
        mScroller = new Scroller(context);

        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        this.setLayoutParams(new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.FILL_PARENT));

       setWillNotDraw(false);
       requestDisallowInterceptTouchEvent(true);
    }


    public ViewFlipper(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DragableSpace);
        mCurrentScreen = a.getInteger(R.styleable.DragableSpace_default_screen, 0);

        mScroller = new Scroller(context);

        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

        this.setLayoutParams(new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT ,
                    ViewGroup.LayoutParams.FILL_PARENT));

        setWillNotDraw(false);
        requestDisallowInterceptTouchEvent(true);
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onTouchEvent will be called and we do the actual
         * scrolling there.
         */

        /*
         * Shortcut the most recurring case: the user is in the dragging state
         * and he is moving his finger. We want to intercept this motion.
         */
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE)
                && (mTouchState != TOUCH_STATE_REST)) {
            return true;
                }

        final float x = ev.getX();

        switch (action) {
            case MotionEvent.ACTION_MOVE:
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                 * Locally do absolute value. mLastMotionX is set to the y value
                 * of the down event.
                 */
                final int xDiff = (int) Math.abs(x - mLastMotionX);
                boolean xMoved = xDiff > mTouchSlop + 50;

                if (xMoved) {
                    // Scroll if the user moved far enough along the X axis then
                    mTouchState = TOUCH_STATE_SCROLLING;
                }
                break;

            case MotionEvent.ACTION_DOWN:
                // Remember location of down touch
                mLastMotionX = x;
                /*
                 * If being flinged and user touches the screen, initiate drag;
                 * otherwise don't.  mScroller.isFinished should be false when
                 * being flinged.
                 */
                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // Release the drag
                mTouchState = TOUCH_STATE_REST;
                break;
        }

        /*
         * The only time we want to intercept motion events is if we are in the
         * drag mode.
         */
        return mTouchState != TOUCH_STATE_REST;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);

        final int action = event.getAction();
        final float x = event.getX();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.i(LOG_TAG, "event : down");
                /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionX = x;

                break;
            case MotionEvent.ACTION_MOVE:
                // Scroll to follow the motion event
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                if (deltaX < 0) {
                    if (mScrollX > 0) {
                        BadScrollHelp.setScrollX(deltaX);
                        scrollBy(Math.max(-mScrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(getChildCount() - 1)
                        .getRight()
                        - mScrollX - getWidth();
                    if (availableToScroll > 0) {
                        BadScrollHelp.setScrollX(deltaX);
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }

                // Request a redraw
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                Log.i(LOG_TAG, "event : up");
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000);
                int velocityX = (int) velocityTracker.getXVelocity();

                if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                    // Fling hard enough to move left
                    snapToScreen(mCurrentScreen - 1);
                } else if (velocityX < -SNAP_VELOCITY
                        && mCurrentScreen < getChildCount() - 1) {
                    // Fling hard enough to move right
                    snapToScreen(mCurrentScreen + 1);
                } else {
                    snapToDestination();
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                mTouchState = TOUCH_STATE_REST;
                //neu unten
                invalidate();  // Request redraw
                break;
            case MotionEvent.ACTION_CANCEL:
                Log.i(LOG_TAG, "event : cancel");
                mTouchState = TOUCH_STATE_REST;
        }
        mScrollX = this.getScrollX();

        return true;
    }

    private void snapToDestination() {
        final int screenWidth = getWidth();
        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
        Log.i(LOG_TAG, "from des");
        snapToScreen(whichScreen);
    }

    public void snapToScreen(int whichScreen) {         
        Log.i(LOG_TAG, "snap To Screen " + whichScreen);
        mCurrentScreen = whichScreen;
        BadScrollHelp.setCurrentScreen(mCurrentScreen);
        final int newX = whichScreen * getWidth();
        final int delta = newX - mScrollX;
        mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);

        invalidate();
    }

    public void setToScreen(int whichScreen) {
        Log.i(LOG_TAG, "set To Screen " + whichScreen);
        mCurrentScreen = whichScreen;
        BadScrollHelp.setCurrentScreen(mCurrentScreen);
        final int newX = whichScreen * getWidth();
        mScroller.startScroll(newX, 0, 0, 0, 10);             
        invalidate();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                child.layout(childLeft, 0, childLeft + childWidth, child
                        .getMeasuredHeight());
                childLeft += childWidth;
            }
        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            //throw new IllegalStateException("error mode.");
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
            //throw new IllegalStateException("error mode.");
        }

        // The children are given the same width and height as the workspace
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
        }
        Log.i(LOG_TAG, "moving to screen "+mCurrentScreen);
        scrollTo(mCurrentScreen * width, 0);    
    }  

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            mScrollX = mScroller.getCurrX();
            scrollTo(mScrollX, 0);
            postInvalidate();
        }
    }

    /**
     * Return the parceable instance to be saved
     */
    @Override
    protected Parcelable onSaveInstanceState() {
      final SavedState state = new SavedState(super.onSaveInstanceState());
      state.currentScreen = mCurrentScreen;
      return state;
    }


    /**
     * Restore the previous saved current screen
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
      SavedState savedState = (SavedState) state;
      super.onRestoreInstanceState(savedState.getSuperState());
      if (savedState.currentScreen != -1) {
        mCurrentScreen = savedState.currentScreen;
        BadScrollHelp.setCurrentScreen(mCurrentScreen);
      }
    }

    // ========================= INNER CLASSES ==============================

    public interface onViewChangedEvent{      
      void onViewChange (int currentViewIndex);
    }

    /**
     * A SavedState which save and load the current screen
     */
    public static class SavedState extends BaseSavedState {
      int currentScreen = -1;

      /**
       * Internal constructor
       * 
       * @param superState
       */
      SavedState(Parcelable superState) {
        super(superState);
      }

      /**
       * Private constructor
       * 
       * @param in
       */
      private SavedState(Parcel in) {
        super(in);
        currentScreen = in.readInt();
      }

      /**
       * Save the current screen
       */
      @Override
      public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeInt(currentScreen);
      }

      /**
       * Return a Parcelable creator
       */
      public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
        public SavedState createFromParcel(Parcel in) {
          return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
          return new SavedState[size];
        }
      };
    }
}

<强> BadScrollHelp.java

package de.android.projects;

public class BadScrollHelp {
    private static int scrollX = 0;
    private static int currentScreen = 0;

    public static synchronized void setScrollX(int scroll){
        scrollX = scroll;
    }

    public static synchronized void setCurrentScreen(int screen){
        currentScreen = screen;
    }

    public static synchronized int getScrollX(){
        return scrollX;
    }

    public static synchronized int getCurrentScreen(){
        return currentScreen;
    }

}

<强> main.xml中

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <de.android.projects.MoveBackground
        xmlns:app="http://schemas.android.com/apk/res/de.android.projects"
        android:id="@+id/space1"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" >   

        <de.android.projects.ViewFlipper
            xmlns:app="http://schemas.android.com/apk/res/de.android.projects"
            android:id="@+id/space"
            android:layout_width="fill_parent" 
            android:layout_height="fill_parent" 
            app:default_screen="0" >
            <include android:id="@+id/left" layout="@layout/left_screen" />
            <include android:id="@+id/center" layout="@layout/initial_screen" />
            <include android:id="@+id/right" layout="@layout/right_screen" />
        </de.android.projects.ViewFlipper>

    </de.android.projects.MoveBackground>

</FrameLayout>

left_screen.xml,right_screen.xml和initial_screen.xml

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="#00000000"
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"
    android:orientation="vertical" >

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

        <Button android:layout_width="100dip"
                android:layout_height="50dip"
                android:text="Button" />

    </LinearLayout>

</LinearLayout>

attrs.xml(在值文件夹中)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DragableSpace">
        <attr name="default_screen" format="integer"/>
    </declare-styleable>
</resources>

所以,最后,就是这样。 onDraw方法仍需要进行一些修改。 到目前为止,我对此解决方案不满意,因为我认为必须可以在onDraw中使用ViewGroup方法。但我无法理解。 此外,使用静态方法设置变量似乎是一个肮脏的技巧。

如果有人可以告诉我如何将这些事件从ViewFlipper传递到父MoveBackground课程,我会很高兴。或者如何将MoveBackground绘图方法包含到ViewFlipper

我可以将ViewFlipper类集成到MoveBackground类中,并以编程方式执行addView(viewFlipper)。比我不再需要那个静态解决方法了。 :)

答案 1 :(得分:10)

有点老问题......

我实施了类似的内容,也覆盖了onDraw中的ViewPager

我发布了所有代码here

答案 2 :(得分:5)

实现此目的的最简单方法是在传递给onDraw(Canvas)的Canvas上自己绘制图像。这是Launcher在介绍壁纸偏移API之前所做的事。

答案 3 :(得分:0)

非常有用的解决方案Rainer,非常感谢,这真的很有帮助,但我注意到了

Drawable.draw(canvas) 

相比,

非常慢

Canvas.drawBitmap(bitmap,srcRect,dstRect,mPaint)

尝试一下!