web-dev-qa-db-ja.com

Android:ビューをナビゲートしながら背景画像を移動する

私が欲しいのは背景画像で、これはストックのホーム画面や天気アプリのように動作します: https://youtube.com/watch?v=i2Oh4GL5wBE#t=0m54s

別のビューにスワイプしながら「少し」スクロールする、アニメーション化されていない背景画像(そのビデオの道路など)が必要です。私のアプリでは、背景をスクロールしていくつかのリストビューをスワイプしたいと思います。

私がテストしたもの:

  • ViewFlipper:大きな画像を使用して、垂直方向に5つにカットしました。次に、各レイアウト(リストビューを表示)でカットされた画像を背景として設定します。
  • Horizo​​ntalScrollView:Horizo​​ntalScrollViewで、LinearLayoutを追加し、大きな画像を背景として設定しました。次に、GestureDectectorを使用して、ListViewをスワイプするときに「スナップ」を追加しました。

どちらも機能しましたが、それには非常に大きな画像が必要であり、さまざまな画面サイズに正しく拡大縮小されるかどうかはわかりません。また、前景が次のビューに変わる間、背景が少しだけ移動する元のホーム画面のようには動作しません。

私はここで投稿を読みました: 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 yOffset)壁紙が特定のウィンドウの後ろに表示されている場合に、より大きなスペース内の現在の壁紙の位置を設定します。

しかし、私が理解している限り、これは電話自体の背景画像を変更するだけです。

私に何ができる?同じことをするオープンソースアプリケーションまたはサンプルコードを知っていますか?おそらく私は自分でCanvasを描く必要があります(これまでに描いたことはありません)。アドバイスをお願いします。

14
Rainer

最後に、私はそれを機能させました(まだいくつかの変更が必要です)。 :)

ViewGroup自体に背景を描くことができなかったことを認めなければなりません。まあ、うまくいきましたが、スクロールを制御できませんでした(スクロールしすぎました)。私が基本的にしたことは、2つのチュートリアルをマージすることでした。

  • ここからの最初のもの: Android Homescreen 異なるレイアウトを切り替えます。そこでまず、透明な背景を設定します。
  • ここから2番目: ドラッグ可能なシンボル タッチによって画面上にシンボルをドラッグします(提案されているように、onDrawを使用)。シンボルを1つに減らし、ImageViewからLinearLayoutに変更しました。

そのレイアウトで、私はViewGroupを配置し、onTouchEventonInterceptTouchEventにいくつかの小さな変更を加え、いくつかの悪いコードを追加し、ついに機能しました。 :)

誰かが興味を持ったら、コードをクリーンアップしてここに投稿しますが、コーディングスタイルが恥ずかしくて、混乱しています。 ;)

どうもありがとうございました、私はあなたの助けに感謝します! :)

UPDATE:つまり、コードは次のとおりです。

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メソッドにはまだいくつかの変更が必要です。 onDrawViewGroupメソッドを使用できる必要があるため、これまでのところこのソリューションには不満があると思います。しかし、私はそれを理解することができませんでした。また、静的メソッドで変数を設定するのは汚いトリックのようです。

誰かがそれらのイベントをViewFlipperから親のMoveBackgroundクラスに渡す方法についてアドバイスをくれたら嬉しいです。または、MoveBackground描画メソッドをViewFlipperに含める方法。

ViewFlipperクラスをMoveBackgroundクラスに統合し、プログラムでaddView(viewFlipper)を実行できます。私よりも、その静的な回避策はもう必要ありません。 :)

10
Rainer

ちょっと古い質問...

同様の実装を行い、onDrawViewPagerもオーバーライドしました。

私はすべてのコードを投稿しました ここ

10
Matthieu

これを実現する最も簡単な方法は、onDraw(Canvas)に渡されたCanvasに自分で画像を描画することです。これは、壁紙オフセットAPIを導入する前にLauncherが行っていたことです。

5
Romain Guy

非常に便利なソリューションRainer、どうもありがとう、これは本当に役に立ちましたが、私はそれに気づきました

Drawable.draw(canvas) 

と比較すると本当に遅いことがわかります

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

やってみて!

0
Soham