2011-01-23 14 views
14

Những gì tôi muốn có là một hình ảnh nền, trong đó hoạt động như màn hình chính chứng khoán hoặc các ứng dụng thời tiết ở đây: https://youtube.com/watch?v=i2Oh4GL5wBE#t=0m54sAndroid: Di chuyển hình nền trong khi điều hướng thông qua xem

tôi chỉ cần một hình ảnh nền không sinh động (như đường trong video đó) sẽ cuộn "một chút" trong khi vuốt sang chế độ xem khác. Trong ứng dụng của tôi, tôi muốn vuốt qua một số ListView với nền cuộn.

Những gì tôi đã thử nghiệm:

  • ViewFlipper: Tôi sử dụng hình ảnh lớn và cắt nó theo chiều dọc thành 5 mảnh. Sau đó, tôi thiết lập những hình ảnh cutted trong từng Layout (hiển thị ListViews) làm nền.
  • HorizontalScrollView: Trong HorizontalScrollView tôi đã thêm LinearLayout và đặt hình ảnh lớn làm nền. Sau đó, tôi đã sử dụng GestureDectector để thêm "chụp" khi vuốt qua các ListView.

Cả hai đều hoạt động, nhưng điều đó đòi hỏi một hình ảnh thực sự lớn và tôi không chắc liệu nó có cân đúng với các kích thước màn hình khác nhau hay không. Ngoài ra nó không hoạt động giống như màn hình chính ban đầu, nơi nền chuyển động chỉ một chút trong khi tiền cảnh thay đổi sang Chế độ xem tiếp theo.

Tôi đọc bài ở đây: https://stackoverflow.com/questions/2943619/android-make-application-background-behave-like-homescreen-background

Tôi đã kiểm tra Launcher.java đề nghị dẫn tôi đến Workspace.java:

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

Theo như tôi hiểu, nó sử dụng WallpaperManager bằng cách đặt Offsets.

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

API nói:

setWallpaperOffsets (IBinder windowToken, nổi xoffset, nổi yOffset) Đặt vị trí của các hình nền hiện tại trong bất kỳ không gian lớn hơn, khi nền đó là có thể nhìn thấy phía sau cửa sổ đã cho.

Nhưng theo như tôi hiểu, điều này chỉ thay đổi hình nền của điện thoại.

Tôi có thể làm gì? Bạn có biết một ứng dụng mã nguồn mở hoặc mã mẫu nào không? Có lẽ tôi phải tự vẽ Canvas (không bao giờ làm trước đây). Xin vui lòng cho tôi một lời khuyên.

Trả lời

10

Cuối cùng, tôi đã làm việc đó (vẫn cần một số sửa đổi). :)

Tôi phải thừa nhận rằng tôi không thể vẽ nền trong chính bản thân số ViewGroup. Vâng, nó đã hoạt động, nhưng tôi không thể điều khiển được cuộn giấy (cuộn quá nhiều). Những gì tôi về cơ bản đã làm, đã hợp nhất 2 hướng dẫn.

  • Đầu tiên từ đây: Android Homescreen Chuyển đổi giữa các bố cục khác nhau. Vì vậy, trước hết, tôi đặt nền trong suốt.
  • Thứ hai từ đây: Draggable Symbols Nó kéo biểu tượng trên màn hình bằng cách chạm (sử dụng onDraw, như được đề xuất). Tôi đã giảm các biểu tượng thành một biểu tượng duy nhất và thay đổi nó từ ImageView thành LinearLayout.

Trong Layout mà tôi đặt ViewGroup, đã làm một số thay đổi nhỏ trong onTouchEventonInterceptTouchEvent, bổ sung một số mã xấu và cuối cùng nó làm việc. :)

Nếu ai đó quan tâm, tôi sẽ dọn sạch mã và đăng nó ở đây, nhưng tôi xấu hổ về phong cách mã hóa của tôi, đó là một mớ hỗn độn. ;)

Cảm ơn bạn rất nhiều, tôi đánh giá cao sự trợ giúp của bạn! :)

UPDATE: Vì vậy, đây là mã:

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(); 
    } 
} 

ViewFlip per.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 và 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 (trong thư mục các giá trị)

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

Vì vậy, cuối cùng, đó là nó. Phương pháp onDraw vẫn cần một số sửa đổi. Tôi không hài lòng với giải pháp này cho đến nay, bởi vì nó phải có thể sử dụng phương pháp onDraw trong ViewGroup, tôi nghĩ vậy. Nhưng tôi không thể hiểu được. Ngoài ra, việc thiết lập các biến với các phương thức tĩnh có vẻ là một thủ thuật bẩn.

Sẽ rất vui nếu ai đó có thể cho tôi lời khuyên cách chuyển các sự kiện đó từ số ViewFlipper đến lớp cha MoveBackground. Hoặc cách bao gồm phương thức vẽ MoveBackground vào ViewFlipper.

Tôi có thể tích hợp lớp ViewFlipper vào lớp MoveBackground và thực hiện theo cách lập trình addView(viewFlipper). Hơn tôi sẽ không cần giải pháp tĩnh đó nữa. :)

giải pháp
+0

Sẽ rất tốt khi xem mã, chỉ để có một số ý tưởng. Tôi đang nghĩ đến việc có một nền hoạt hình đằng sau bố cục chuẩn, nhưng vẫn đang tìm cách tiếp cận tốt nhất. – Lumis

+1

Vâng, tôi đã chỉnh sửa bài đăng gần đây của mình ở trên. – Rainer

+0

Điều này có vẻ tốt, tuy nhiên tôi muốn thử mã đầu tiên để xem nó có thể làm gì. Hình nền của bạn lớn đến mức nào? Và bạn có thể đăng R.styleable.DragableSpace và DragableSpace_default_screen như là mất tích ... Exlipse cũng phàn nàn về ứng dụng: default_screen = "0" trong main.xml – Lumis

5

Cách đơn giản nhất để đạt được điều này là tự vẽ hình ảnh trên Canvas được chuyển đến onDraw(Canvas). Đây là những gì Launcher được sử dụng để làm trước khi chúng tôi giới thiệu các offsets hình nền API.

+0

Được rồi. Tôi nghĩ tôi phải đi theo cách này. Tôi sẽ thử vào ngày mai. Cảm ơn :) – Rainer

+0

Sau đó, câu hỏi có thể thêm một lượt xem bình thường trên một khung vẽ không? – Lumis

+1

Có, bạn có thể, chỉ cần sử dụng một ViewGroup. –

0

Rất hữu ích Rainer, thanks a lot, đây là thực sự hữu ích nhưng tôi nhận thấy rằng

Drawable.draw(canvas) 

làm ra là rất chậm khi so sánh với

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

thử nó ra!

10

Loại câu hỏi cũ ...

tôi thực hiện một cái gì đó tương tự, cũng trọng onDraw trong ViewPager.

Tôi đã đăng tất cả mã here

+0

Có cách nào để làm điều này, nhưng với hai hình ảnh? –

+0

Bạn có nghĩa là có một hình ảnh bên cạnh hình ảnh khác? Tôi chắc rằng có thể thay đổi mã để làm điều đó ... – Matthieu

Các vấn đề liên quan