Android: перемещение фонового изображения при навигации по представлениям - PullRequest
14 голосов
/ 23 января 2011

То, что я хочу получить, это фоновое изображение, которое ведет себя как обычный рабочий стол или приложение погоды здесь: https://youtube.com/watch?v=i2Oh4GL5wBE#t=0m54s

Мне просто нужно не анимированное фоновое изображение (например, дорога в этом видео), которое прокручивается «немного» при переходе к другому виду. В моем приложении я хотел бы пролистать некоторые ListViews с фоном прокрутки.

Что я проверял:

  • ViewFlipper: я использовал большое изображение и разрезал его вертикально на 5 частей. Затем я установил эти вырезанные изображения в каждом макете (отображая списки) в качестве фона.
  • HorizontalScrollView: в HorizontalScrollView я добавил LinearLayout и установил большое изображение в качестве фона. Затем я использовал GestureDectector для добавления «привязки» при пролистывании через ListViews.

Оба сработали, но для этого требуется действительно большое изображение, и я не уверен, правильно ли оно масштабируется до разных размеров экрана. Кроме того, он не ведет себя как исходный домашний экран, фон которого немного перемещается, а передний план меняется на следующий вид.

Я прочитал пост здесь: 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, установив Offsets.

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

API говорит:

setWallpaperOffsets (IBinder windowToken, float xOffset, float YOffset) Установите положение текущие обои внутри любого большего пространство, когда эти обои видны за данным окном.

Но, насколько я понимаю, это просто меняет фоновое изображение самого телефона.

Что я могу сделать? Знаете ли вы приложение с открытым исходным кодом или пример кода, который делает то же самое? Вероятно, я должен нарисовать холст сам (никогда не делал раньше). Пожалуйста, дайте мне совет.

Ответы [ 4 ]

10 голосов
/ 05 февраля 2013

Вид старого вопроса ...

Я реализовал нечто подобное, также переопределяя onDraw в ViewPager.

Я разместил весь код здесь

10 голосов
/ 27 января 2011

Наконец-то у меня все заработало (все еще нужны некоторые модификации).:)

Я должен признать, что не смог нарисовать фон в самом ViewGroup.Ну, это сработало, но я не мог контролировать прокрутку (слишком много прокручивал).Что я в основном сделал, так это слияние двух учебных пособий.

  • Первый из них: Домашний экран Android Он переключается между различными макетами.Итак, во-первых, я установил прозрачные фоны.
  • Второй отсюда: Перетаскиваемые символы Он перетаскивает символы по экрану касанием (используя onDraw, как предложено).Я уменьшил символы до одного и изменил его с ImageView на LinearLayout.

В этом макете я поместил ViewGroup, сделал небольшие изменения в onTouchEvent и onInterceptTouchEvent, добавил какой-то плохой код и наконец он заработал.:)

Если кому-то интересно, я приведу в порядок код и выложу его здесь, но мне стыдно за мой стиль кодирования, это беспорядок.;)

Большое спасибо, я ценю вашу помощь!:)

ОБНОВЛЕНИЕ: Итак, вот код:

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) программно.Чем мне больше не нужен этот статический обходной путь.:)

5 голосов
/ 23 января 2011

Самый простой способ добиться этого - нарисовать изображение на холсте, переданном в onDraw(Canvas).Это то, что раньше делал Launcher до того, как мы представили API смещения обоев.

0 голосов
/ 23 мая 2011

Очень полезное решение Райнер, большое спасибо, это было очень полезно, но я заметил, что

Drawable.draw(canvas) 

работает очень медленно по сравнению с

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

попробуй!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...