Android: перемещение фонового изображения при навигации через Views

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

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

Что я тестировал:

  • ViewFlipper: Я использовал большое изображение и разрезал его по вертикали на 5 штук. Затем я устанавливал эти отрезанные изображения в каждом макете (отображая ListViews) в качестве фона.
  • HorizontalScrollView: в HorizontalScrollView я добавил 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, устанавливая Offsets.

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

API говорит:

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

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

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

Solutions Collecting From Web of "Android: перемещение фонового изображения при навигации через Views"

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

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

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

Наконец, я получил его работу (по-прежнему нужны некоторые модификации). 🙂

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

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

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

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

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

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 все еще нуждается в некоторой модификации. Я до сих пор недоволен этим решением, потому что, по- onDraw для использования метода onDraw в ViewGroup должно быть возможно. Но я не мог понять. Кроме того, установка переменных со статическими методами кажется грязной трюкой.

Был бы рад, если бы кто-нибудь мог дать мне совет, как передать эти события из ViewFlipper в родительский класс MoveBackground . Или как включить MoveBackground рисования MoveBackground в ViewFlipper .

Я мог бы интегрировать класс ViewFlipper класс MoveBackground и делать addView(viewFlipper) программно. Чем больше мне не понадобится это статическое обходное решение. 🙂

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

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

 Drawable.draw(canvas) 

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

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

Попробуйте!