Разработка домашнего экрана Android

Я работаю над приложением, которое имеет домашний экран. Этот главный экран должен вести себя как главный экран Android, где вы можете переключаться между несколькими представлениями, проводя пальцем по сенсорному экрану.

Решение простое. У меня есть 3 экземпляра вида, справа, слева и текущий вид. Я получаю эти экземпляры от viewflipper, который я инициализировал ранее. Поскольку у меня HTC G1, мой экран имеет ширину 320 пикселей и высоту 480 пикселей.

Представьте, что вы фиксируете значение вниз для события движения вниз, когда вы касаетесь экрана. Затем вы двигаете пальцем, и экран должен двигаться точно так же, поэтому вам нужно пересчитать положение представления. Это работает для меня до сих пор, но я столкнулся со странной проблемой. Когда вы касаетесь правого вида, не двигая пальцем, но удерживая его на экране меньше секунды, вид исчезает и показывает левый вид.

Вот мой код:

public class MainActivity extends Activity implements OnTouchListener{

    private ViewFlipper vf;
    private float downXValue;
    private View view1, view2, view3;

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

        this.vf = (ViewFlipper) findViewById(R.id.flipper);

        if(this.vf != null){
             this.view1 = vf.getChildAt(0);
             this.view2 = vf.getChildAt(1);  
             this.view3 = vf.getChildAt(2);
             vf.setDisplayedChild(0);
         }      

         LinearLayout layMain = (LinearLayout) findViewById(R.id.layout_main);
         layMain.setOnTouchListener((OnTouchListener) this);
    }

    public boolean onTouch(View v, MotionEvent arg1) {

         final View currentView = vf.getCurrentView();
         final View leftView, rightView;

         if(currentView == view1){
             leftView = view3;
             rightView = view2;
         }else if(currentView == view2){
             leftView = view1;
             rightView = view3;
         }else if(currentView == view3){
             leftView = view2;
             rightView = view1;
         }else{
             leftView = null;
             rightView = null;
         }

         switch (arg1.getAction()){
            case MotionEvent.ACTION_DOWN:{
                this.downXValue = arg1.getX();
                break;
            }
            case MotionEvent.ACTION_UP:{
                float currentX = arg1.getX();            
                    if ((downXValue < currentX)){
                        if(currentView != view3){
                        float t3 = (320-(currentX-downXValue))/320;                             
                        this.vf.setInAnimation(AnimationHelper.inFromLeftAnimation(t3));
                        this.vf.setOutAnimation(AnimationHelper.outToRightAnimation(t3));
                        this.vf.showPrevious(); } 
                      }

                    if ((downXValue > currentX)){
                        if(currentView != view2){
                        float t = (320-(downXValue-currentX))/320;
                        this.vf.setInAnimation(AnimationHelper.inFromRightAnimation(t));
                        this.vf.setOutAnimation(AnimationHelper.outToLeftAnimation(t));
                        this.vf.showNext();}    
                    }                         
            }
            break;
            case MotionEvent.ACTION_MOVE:{

                leftView.setVisibility(View.VISIBLE);
                rightView.setVisibility(View.VISIBLE);

                float currentX = arg1.getX();     
                if(downXValue > currentX){  
                    if(currentView != view2){
                        currentView.layout((int) (currentX - downXValue),
                        currentView.getTop(),
                        (int) (currentX - downXValue) + 320,
                        currentView.getBottom()); 
                    }
                }

                if(downXValue < currentX){  
                    if(currentView != view3){
                        currentView.layout((int) (currentX - downXValue),
                        currentView.getTop(),
                        (int) (currentX - downXValue) + 320,
                        currentView.getBottom());


                    }
                }
                leftView.layout(currentView.getLeft()-320, leftView.getTop(),
                       currentView.getLeft(), leftView.getBottom());   

                rightView.layout(currentView.getRight(), rightView.getTop(), 
                        currentView.getRight() + 320, rightView.getBottom());
                }
            }

        return true;
    }

    public static class AnimationHelper {
          public static Animation inFromRightAnimation(float param) {
            Animation inFromRight = new TranslateAnimation(
            Animation.RELATIVE_TO_PARENT, +param,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f);
            inFromRight.setDuration(250);
            inFromRight.setInterpolator(new AccelerateInterpolator());
            return inFromRight;
          }

          public static Animation outToLeftAnimation(float param) {
            Animation outtoLeft = new TranslateAnimation(
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, -param,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f);
            outtoLeft.setDuration(250);
            outtoLeft.setInterpolator(new AccelerateInterpolator());
            return outtoLeft;
          }

          // for the next movement
          public static Animation inFromLeftAnimation(float param) {
            Animation inFromLeft = new TranslateAnimation(
            Animation.RELATIVE_TO_PARENT, -param,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f);
            inFromLeft.setDuration(250);
            inFromLeft.setInterpolator(new AccelerateInterpolator());
            return inFromLeft;
          }

          public static Animation outToRightAnimation(float param) {
            Animation outtoRight = new TranslateAnimation(
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, +param,
            Animation.RELATIVE_TO_PARENT, 0.0f,
            Animation.RELATIVE_TO_PARENT, 0.0f);
            outtoRight.setDuration(250);
            outtoRight.setInterpolator(new AccelerateInterpolator());
            return outtoRight;
          }
        }

}

Я думаю, что такой Homescreen — интересный элемент пользовательского интерфейса.

Любые идеи?


person Upvote    schedule 12.08.2010    source источник
comment
Можете ли вы повторно загрузить свой проект eclipse, потому что megaupload.com закрыт?   -  person Kalpesh    schedule 03.07.2012


Ответы (3)


ИЗМЕНИТЬ (3 июля 2012 г.):

Поскольку, похоже, по этому ответу все еще есть довольно много мнений и комментариев, я подумал, что должен добавить примечание о том, что с более новым SDK теперь вы должны использовать ViewPager, чтобы иметь ту же функциональность. Этот класс также включен в библиотеку поддержки Android, поэтому вы можете также используйте его для запуска на более ранних устройствах Android.

ИЗМЕНИТЬ (4 марта 2013 г.):

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

Если вы действительно хотите сделать все это вручную, исходный ответ здесь ниже...

Я думаю, вы можете найти то, что ищете, здесь: http://www.anddev.org/why_do_not_these_codes_work-t4012.html

Я использовал это в другом проекте, чтобы также создать домашний экран с разными представлениями. Это прямо из Android Launcher, он работает довольно хорошо после того, как следил за этой веткой.

Вот мой код... сначала исходный код

package com.matthieu.launcher;

import android.content.Context;
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;

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

    public DragableSpace(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);

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

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

        TypedArray a=getContext().obtainStyledAttributes(attrs,R.styleable.DragableSpace);
        mCurrentScreen = a.getInteger(R.styleable.DragableSpace_default_screen, 0);
    }

    @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;

                if (xMoved) {
                    // Scroll if the user moved far enough along the X axis
                    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:
                // Log.i(LOG_TAG,"event : move");
                // if (mTouchState == TOUCH_STATE_SCROLLING) {
                // Scroll to follow the motion event
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                //Log.i(LOG_TAG, "event : move, deltaX " + deltaX + ", mScrollX " + mScrollX);

                if (deltaX < 0) {
                    if (mScrollX > 0) {
                        scrollBy(Math.max(-mScrollX, deltaX), 0);
                    }
                } else if (deltaX > 0) {
                    final int availableToScroll = getChildAt(getChildCount() - 1)
                        .getRight()
                        - mScrollX - getWidth();
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }
                // }
                break;
            case MotionEvent.ACTION_UP:
                Log.i(LOG_TAG, "event : up");
                // if (mTouchState == TOUCH_STATE_SCROLLING) {
                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;
                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;
        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;
        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();
        }
    }
}

И файл макета:

<?xml version="1.0" encoding="utf-8"?>
<com.matthieu.launcher.DragableSpace xmlns:app="http://schemas.android.com/apk/res/com.matthieu.launcher"
    xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/space"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:default_screen="1"
>
<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" />
</com.matthieu.launcher.DragableSpace>

Чтобы иметь дополнительный атрибут в файле xml, вы хотите сохранить его в res/values/attrs.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="DragableSpace">
        <attr name="default_screen" format="integer"/>
    </declare-styleable>
</resources>
person Matthieu    schedule 17.10.2010
comment
привет еще раз, у меня есть некоторые проблемы с этим impementation. У меня не работает, ошибок нет. Приложение просто появляется с кнопкой принудительного закрытия. не могли бы вы загрузить рабочий пример, пожалуйста.. - person Upvote; 23.11.2010
comment
Этот код сам по себе отлично работает в моем приложении. Можете ли вы загрузить журнал в pastebin или что-то в этом роде? - person Matthieu; 23.11.2010
comment
ArtWorkAD, вставленный вами журнал жалуется на запуск действия под названием ComponentInfo, я не могу найти ничего подобного в опубликованном коде... Можете ли вы проверить, изменили ли вы что-нибудь? - person Matthieu; 24.11.2010
comment
взгляните на этот скриншот img213.imageshack.us/img213/7273/screenmv.png когда я просто копирую ваш код, кажется, что-то не так с файлом xml - person Upvote; 24.11.2010
comment
Это потому, что в нем отсутствует XML-файл attrs.xml, это нужно только для использования поля «приложение: default_screen». Я предполагаю, что вы переименовали путь к пакету, просто замените часть «com.matthieu.launcher» на «android.projects.de» (похоже, это имя вашего пакета из другого отправленного вами журнала) - person Matthieu; 25.11.2010
comment
У меня сейчас точно такой же код и тот же xml, и он не работает, посмотрите мой проект целиком android-projects.de/DragableSpace.rar - person Upvote; 25.11.2010
comment
У вас есть адрес электронной почты или способ связаться напрямую? Здесь День Благодарения... посмотрю на следующей неделе. - person Matthieu; 25.11.2010
comment
Именно то, что мне нужно! Спасибо, Матье! Но должны ли мы добавить a.recycle() после использования TypedArray? - person hungson175; 09.12.2010
comment
Боже пошли! Так приятно работает! Большое спасибо ! (теперь я просто ищу, могу ли я каким-то образом удалить attrs.xml, чтобы сделать его более пригодным для повторного использования) - person tacone; 26.02.2011
comment
Можете ли вы загрузить полное демо для меня? - person Kalpesh; 03.07.2012

Вы можете посмотреть на http://code.google.com/p/andro-views/< /а>

person Kevin Gaudin    schedule 17.10.2010
comment
см. stackoverflow.com/questions/2501307/ за другие комментарии к андро-просмотрам - person Thierry-Dimitri Roy; 11.05.2011

Код Матье очень хорош, но он не сохраняет состояние после изменения ориентации. Чтобы решить эту проблему, добавьте следующий код в класс DragableSpace.

 /**
     * 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;
      }
    }

    // ========================= 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];
        }
      };
    }

я взял это из ответа Кевина.

person Denis Palnitsky    schedule 11.11.2010