1. 程式人生 > >Android 橫向帶有吸附效果的橫向拖動控制元件(效果同縱向下拉重新整理ListView)

Android 橫向帶有吸附效果的橫向拖動控制元件(效果同縱向下拉重新整理ListView)

先上一張效果圖


中間的橫向拖動就是我們要做的效果。

一、實現思路

仔細觀察不難發現,該拖動view與listview的下拉重新整理的效果很類似,手指拖動的時候顯示隱藏的view,手指放開自動回彈。只不過區別就是一個橫向一個縱向 下拉重新整理的實現思路如下: 自定義一個佈局繼承自LinearLayout,然後在這個佈局中加入下拉頭和ListView這兩個子元素,並讓這兩個子元素縱向排列。初始化的時候,讓下拉頭向上偏移出螢幕,這樣我們看到的就只有ListView了。然後對ListView的touch事件進行監聽,如果當前ListView已經滾動到頂部並且手指還在向下拉的話,那就將下拉頭顯示出來,鬆手後進行重新整理操作,並將下拉頭隱藏。原理如圖所示:

我們要實現的效果跟下拉重新整理基本一樣,只不過是方向是橫向而已。如圖

二、具體實現

首先我們要先自定義一個LinearLayout,然後在佈局中加入隱藏頭和剩餘部分 佈局檔案如下:
        <com.boohee.myview.HorizontalDragLinearLayout
            android:id="@+id/main_hori_scroll_ll"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            android:baselineAligned="false"
            android:clickable="true"
            android:descendantFocusability="blocksDescendants"
            android:orientation="horizontal">

            <!--新增一層處理左右拖動時的移動-->
            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                >

              ...........<pre name="code" class="java"><span style="white-space:pre">		</span><!--此處新增 剩餘顯示的部分-->
</LinearLayout> </com.boohee.myview.HorizontalDragLinearLayout> 自定義的LinearLayout,初始化部分:
    private Context context;
    //需要隱藏的view
    private TextView headerView;
    /**
     * 是否已載入過一次layout,這裡onLayout中的初始化只需載入一次
     */
    private boolean loadOnce;
    /**
     * 需要隱藏View的佈局引數
     */
    private MarginLayoutParams headerLayoutParams;

    /**
     * 隱藏view的寬度
     */
    private int hideHeaderWidth;
    private float xDown;
    /**
     * 每次move的x座標
     */
    private float tmpXMove;

    private float mFocusX = 0.f;
    /**
     * 在被判定為滾動之前使用者手指可以移動的最大值。
     */
    private int touchSlop;
    /**
     * 頭部回滾的速度
     */
    public static final int SCROLL_SPEED = -10;

    public HorizontalDragLinearLayout(Context context) {
        this(context, null);
    }

    public HorizontalDragLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalDragLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setOrientation(HORIZONTAL);
        this.context = context;
        init();
    }

    private void init() {
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        headerView = new TextView(context);
        LinearLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.CENTER_VERTICAL;
        headerView.setLayoutParams(params);
        headerView.setTextColor(Color.BLACK);
        headerView.setGravity(Gravity.CENTER);
        this.addView(headerView, 0);
    }

    public void setHeaderText(String string){
        if (headerView != null){
            headerView.setText(string);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (headerView != null && changed && !loadOnce){
            hideHeaderWidth = - headerView.getWidth();
            headerLayoutParams = (MarginLayoutParams) headerView.getLayoutParams();
            headerLayoutParams.leftMargin = hideHeaderWidth;
            headerView.setLayoutParams(headerLayoutParams);
            loadOnce = true;
        }
    }

初始化的時候動態添加了一個TextView,也就是我們需要隱藏的部分 在第一次onLayout佈局的時候,我們根據隱藏頭的寬度 調整該隱藏頭的margin,使其隱藏。 然後我們需要進行拖動識別,重寫onTouchEvent 
    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {
        //禁止父容器攔截事件
        getParent().requestDisallowInterceptTouchEvent(true);
        switch (motionEvent.getAction()){
            case MotionEvent.ACTION_DOWN:
                xDown = motionEvent.getRawX();
                return true;
            case MotionEvent.ACTION_MOVE:
                float xMove = motionEvent.getRawX();
                int distance = (int) (xMove - xDown);
                if (distance <= 0 && headerLayoutParams.leftMargin <= hideHeaderWidth) {
                    return false;
                }
                if (distance < touchSlop) {
                    return false;
                }
                //大於頭部寬度不做處理
                if (distance >= -hideHeaderWidth){
                    return true;
                }
                headerLayoutParams.leftMargin = distance + hideHeaderWidth;
                headerView.setLayoutParams(headerLayoutParams);
                tmpXMove = xMove;
                return true;
            case MotionEvent.ACTION_UP:
                //鬆手時呼叫隱藏頭部
                new HideHeaderTask().execute();
                break;
            default:
                break;
        }
        return true;
    }

由於外層是用的viewPager+fragment進行頁面切換,所以預設情況下touch事件是被viewpager消費掉的。我們要組織父控制元件攔截事件         getParent().requestDisallowInterceptTouchEvent(true);
其中涉及Android 的touch 分發,可以參考鴻洋大大的這篇部落格點選開啟連結 在move的時候得到移動的距離,然後對隱藏頭設定margin,使其及該LinearLayout的其他子view跟隨隱藏頭一起向右移動,達到我們想要的效果 up的時候啟動一個AsyncTask 動態改變隱藏頭的margin,去恢復到最初始的狀態,即隱藏頭隱藏的狀態。
    /**
     * 隱藏頭部
     */
    class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {

        @Override
        protected Integer doInBackground(Void... params) {
            int leftMargin = headerLayoutParams.leftMargin;
            while (true) {
                leftMargin = leftMargin + SCROLL_SPEED;
                if (leftMargin <= hideHeaderWidth) {
                    leftMargin = hideHeaderWidth;
                    break;
                }
                publishProgress(leftMargin);
                sleep(10);
            }
            return leftMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... leftMargin) {
            headerLayoutParams.leftMargin = leftMargin[0];
            headerView.setLayoutParams(headerLayoutParams);
        }

        @Override
        protected void onPostExecute(Integer leftMargin) {
            headerLayoutParams.leftMargin = leftMargin;
            headerView.setLayoutParams(headerLayoutParams);
        }
    }

    private void sleep(int i) {
        try {
            Thread.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

搞定。 另外 效果中帶懸浮進度的progressBar 連線在這裡