Android 自定義view實現上下滑動,大眾點評,美團地圖導航界面。


主函數:

package com.example.slideview;

import com.example.fdadsf.R;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.app.Activity;

/**
 * 滑動菜單Demo主Activity
 * 
 * @author guolin
 */
public class MainActivity extends Activity {

    /**
     * 雙向滑動菜單布局
     */
    private UpAndDownSlidinglayout updownSldingLayout;

    /**
     * 在內容布局上顯示的ListView
     */
    private ListView contentList;
    private LinearLayout ll;
    /**
     * ListView的適配器
     */
    private ArrayAdapter<String> contentListAdapter;

    /**
     * 用於填充contentListAdapter的數據源。
     */
    private String[] contentItems = { "Content Item 1", "Content Item 2",
            "Content Item 3", "Content Item 4", "Content Item 5",
            "Content Item 6", "Content Item 7", "Content Item 8",
            "Content Item 9", "Content Item 10", "Content Item 11",
            "Content Item 12", "Content Item 13", "Content Item 14",
            "Content Item 15", "Content Item 16" };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ll = (LinearLayout) findViewById(R.id.content);
        updownSldingLayout = (UpAndDownSlidinglayout) findViewById(R.id.updown_sliding_layout);
        contentList = (ListView) findViewById(R.id.contentList);
        contentListAdapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, contentItems);
        contentList.setAdapter(contentListAdapter);
        updownSldingLayout.setScrollEvent(ll);
    }

}

自定義上下滑動view:

package com.example.slideview;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;

public class UpAndDownSlidinglayout extends RelativeLayout implements
        OnTouchListener {
    /**
     * 滾動顯示和隱藏上側布局時,手指滑動需要達到的速度。
     */
    public static final int SNAP_VELOCITY = 200;

    /**
     * 滑動狀態的一種,表示未進行任何滑動。
     */
    public static final int DO_NOTHING = 0;
    /**
     * 滑動狀態的一種,表示正在滑出底部界面。
     */
    public static final int SHOW_UP_MENU = 1;

    /**
     * 滑動狀態的一種,表示正在隱藏底部界面。
     */
    public static final int HIDE_UP_MENU = 3;
    /**
     * 滑動狀態的一種,表示底部界面顯示的情況下,繼續向下滑動。
     */
    public static final int SHOW_UP_MENU2 = 4;

    /**
     * 記錄當前的滑動狀態
     */
    private int slideState;

    /**
     * 屏幕寬度值。
     */
    private int screenWidth;
    private int screenHeight;

    /**
     * 在被判定為滾動之前用戶手指可以移動的最大值。
     */
    private int touchSlop;

    /**
     * 記錄手指按下時的橫坐標。
     */
    private float xDown;

    /**
     * 記錄手指按下時的縱坐標。
     */
    private float yDown;

    /**
     * 記錄手指移動時的橫坐標。
     */
    private float xMove;

    /**
     * 記錄手指移動時的縱坐標。
     */
    private float yMove;

    /**
     * 記錄手機抬起時的縱坐標。
     */
    private float yUp;
    /**
     * 底部界面當前是顯示還是隱藏。只有完全顯示或隱藏時才會更改此值,滑動過程中此值無效。
     */
    private boolean isUpMenuVisible;

    /**
     * 下側菜單當前是顯示還是隱藏。只有完全顯示或隱藏時才會更改此值,滑動過程中此值無效。
     */
    private boolean isDownMenuVisible;

    /**
     * 是否正在滑動。
     */
    private boolean isSliding;

    /**
     * 底部界面布局對象。
     */
    private View upMenuLayout;

    /**
     * 內容布局對象。
     */
    private View contentLayout;

    /**
     * 用於監聽滑動事件的View。
     */
    private View mBindView;

    /**
     * 底部界面布局的參數。
     */
    private MarginLayoutParams upMenuLayoutParams;

    /**
     * 內容布局的參數。
     */
    private RelativeLayout.LayoutParams contentLayoutParams;

    /**
     * 用於計算手指滑動的速度。
     */
    private VelocityTracker mVelocityTracker;

    public UpAndDownSlidinglayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();
        screenHeight = wm.getDefaultDisplay().getHeight();
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
     * 綁定監聽滑動事件的View。
     * 
     * @param bindView
     *            需要綁定的View對象。
     */
    public void setScrollEvent(View bindView) {
        mBindView = bindView;
        mBindView.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        createVelocityTracker(event);
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 手指按下時,記錄按下時的坐標
            xDown = event.getRawX();
            yDown = event.getRawY();
            // 將滑動狀態初始化為DO_NOTHING
            slideState = DO_NOTHING;
            break;
        case MotionEvent.ACTION_MOVE:
            xMove = event.getRawX();
            yMove = event.getRawY();
            int moveDistanceX = (int) (xMove - xDown);
            int moveDistanceY = (int) (yMove - yDown);
            // 檢查當前的滑動狀態
            checkSlideState(moveDistanceX, moveDistanceY);
            switch (slideState) {
            case SHOW_UP_MENU:
                contentLayoutParams.topMargin = moveDistanceY;
                contentLayout.setLayoutParams(contentLayoutParams);
                break;
            case SHOW_UP_MENU2:
            case HIDE_UP_MENU:
                contentLayoutParams.topMargin = screenHeight / 2
                        + moveDistanceY;
                contentLayout.setLayoutParams(contentLayoutParams);
                break;
            default:
                break;
            }
            break;
        case MotionEvent.ACTION_UP:
            yUp = event.getRawY();
            int upDistanceY = (int) (yUp - yDown);
            if (isSliding) {
                // 手指抬起時,進行判斷當前手勢的意圖
                switch (slideState) {
                case SHOW_UP_MENU:
                case SHOW_UP_MENU2:
                    if (shouldScrollToUpMenu()) {
                        scrollToUpMenu();
                    } else {
                        scrollToContentFromUpMenu();
                    }
                    break;
                case HIDE_UP_MENU:
                    if (shouldScrollToContentFromUpMenu()) {
                        scrollToContentFromUpMenu();
                    } else {
                        scrollToUpMenu();
                    }
                    break;
                default:
                    break;
                }
            } else if (upDistanceY < touchSlop && isUpMenuVisible) {
                // 當底部界面顯示時,如果用戶點擊一下內容部分,則直接滾動到內容界面
                scrollToContentFromUpMenu();
            }else if (upDistanceY < touchSlop && !isUpMenuVisible) {
                //當上冊菜單隱藏時,如果用戶點擊一下內容部分,則直接顯示底部界面
                scrollToUpMenu();
            }
            recycleVelocityTracker();
            break;
        }
        return true;
    }

    /**
     * 創建VelocityTracker對象,並將觸摸事件加入到VelocityTracker當中。
     * 
     * @param event
     * 
     */
    private void createVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    /**
     * 根據手指移動的距離,判斷當前用戶的滑動意圖,然后給slideState賦值成相應的滑動狀態值。
     * 
     * @param moveDistanceX
     *            橫向移動的距離
     * @param moveDistanceY
     *            縱向移動的距離
     */
    private void checkSlideState(int moveDistanceX, int moveDistanceY) {
        if (isUpMenuVisible) {
            if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
                    && moveDistanceY < 0) {
                isSliding = true;
                slideState = HIDE_UP_MENU;
            } else if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
                    && moveDistanceY > 0) {
                isSliding = true;
                slideState = SHOW_UP_MENU2;
            }
        } else {
            if (!isSliding && Math.abs(moveDistanceY) >= touchSlop
                    && moveDistanceY > 0 && Math.abs(moveDistanceX) < touchSlop) {
                isSliding = true;
                slideState = SHOW_UP_MENU;
                contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0);
                contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
                contentLayout.setLayoutParams(contentLayoutParams);
            }
        }
    }

    /**
     * 判斷是否應該滾動將底部界面展示出來。如果手指移動距離大於屏幕寬度的1/4,或者手指移動速度大於SNAP_VELOCITY,
     * 就認為應該滾動將底部界面展示出來。
     * 
     * @return 如果應該將底部界面展示出來返回true,否則返回false。
     */
    private boolean shouldScrollToUpMenu() {
        return yUp - yDown > upMenuLayoutParams.height / 4
                || getScrollVelocity() > SNAP_VELOCITY;
    }

    /**
     * 判斷是否應該從底部界面滾動到內容布局,如果手指移動距離大於屏幕寬度的1/4,或者手指移動速度大於SNAP_VELOCITY,
     * 就認為應該從底部界面滾動到內容布局。
     * 
     * @return 如果應該從底部界面滾動到內容布局返回true,否則返回false。
     */
    private boolean shouldScrollToContentFromUpMenu() {
        return yDown - yUp > upMenuLayoutParams.height / 4
                || getScrollVelocity() > SNAP_VELOCITY;
    }

    /**
     * 獲取手指在綁定布局上的滑動速度。
     * 
     * @return 滑動速度,以每秒鍾移動了多少像素值為單位。
     */
    private int getScrollVelocity() {
        mVelocityTracker.computeCurrentVelocity(1000);
        int velocity = (int) mVelocityTracker.getXVelocity();
        return Math.abs(velocity);
    }

    class UpMenuScrollTask extends AsyncTask<Integer, Integer, Integer> {

        @Override
        protected Integer doInBackground(Integer... speed) {
            int topMargin = contentLayoutParams.topMargin;
            // 根據傳入的速度來滾動界面,當滾動到達邊界值時,跳出循環。
            while (true) {
                topMargin = topMargin - speed[0];
                if (topMargin > screenHeight / 2) {
                    topMargin = screenHeight / 2;
                    break;
                }
                if (topMargin < 0) {
                    topMargin = 0;
                    break;
                }
                publishProgress(topMargin);
                // 為了要有滾動效果產生,每次循環使線程睡眠一段時間,這樣肉眼才能夠看到滾動動畫。
                sleep(20);
            }
            if (speed[0] > 0) {
                isUpMenuVisible = false;
            } else {
                isUpMenuVisible = true;
            }
            isSliding = false;
            return topMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... topMargin) {
            contentLayoutParams.topMargin = topMargin[0];
            contentLayout.setLayoutParams(contentLayoutParams);
            unFocusBindView();
        }

        @Override
        protected void onPostExecute(Integer topMargin) {
            contentLayoutParams.topMargin = topMargin;
            contentLayout.setLayoutParams(contentLayoutParams);
        }
    }

    /**
     * 使當前線程睡眠指定的毫秒數。
     * 
     * @param millis
     *            指定當前線程睡眠多久,以毫秒為單位
     */
    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用可以獲得焦點的控件在滑動的時候失去焦點。
     */
    private void unFocusBindView() {
        if (mBindView != null) {
            mBindView.setPressed(false);
            mBindView.setFocusable(false);
            mBindView.setFocusableInTouchMode(false);
        }
    }

    /**
     * 將界面滾動到底部界面界面,滾動速度設定為-30.
     */
    public void scrollToUpMenu() {
        new UpMenuScrollTask().execute(-30);
    }

    /**
     * 將界面從底部界面滾動到內容界面,滾動速度設定為30.
     */
    public void scrollToContentFromUpMenu() {
        new UpMenuScrollTask().execute(30);
    }

    /**
     * 在onLayout中重新設定底部界面、下側菜單、以及內容布局的參數。
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            // 獲取底部界面布局對象
            upMenuLayout = getChildAt(0);
            upMenuLayoutParams = (MarginLayoutParams) upMenuLayout
                    .getLayoutParams();
            // 獲取內容布局對象
            contentLayout = getChildAt(1);
            contentLayoutParams = (RelativeLayout.LayoutParams) contentLayout
                    .getLayoutParams();
            contentLayoutParams.height = screenHeight;
            contentLayout.setLayoutParams(contentLayoutParams);
        }
    }

    /**
     * 回收VelocityTracker對象。
     */
    private void recycleVelocityTracker() {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }
}

XML文件:

<com.example.slideview.UpAndDownSlidinglayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/updown_sliding_layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <RelativeLayout
        android:id="@+id/up_menu"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:background="#00ccff"
         >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="This is up menu"
            android:textColor="#000000"
            android:textSize="28sp" />
    </RelativeLayout>
    <LinearLayout
        android:id="@+id/content"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:paddingTop="50dp"
        android:gravity="center"
        android:background="#00ff00" >

        <ListView
            android:id="@+id/contentList"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:background="#e9e9e9"
            android:cacheColorHint="#00000000"
            android:scrollbars="none" >

        </ListView>
    </LinearLayout>

</com.example.slideview.UpAndDownSlidinglayout>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM