1. 程式人生 > >android 自定義view之側滑效果

android 自定義view之側滑效果

效果圖:

這裡寫圖片描述

看網上的都是兩個view拼接,預設右側的不顯示,水平移動的時候把右側的view顯示出來。但是看最新版QQ上的效果不是這樣的,但給人的感覺卻很好,所以獻醜來一發比較高仿的。

知識點:

1、ViewDragHelper 的用法;
2、滑動衝突的解決;
3、自定義viewgroup。

ViewDragHelper 出來已經比較久了 相信大家都比較熟悉,不熟悉的話google一大把
這裡主要簡單用一下它的幾個方法
1、tryCaptureView(View child, int pointerId) :確定那個子view可以滑動

2、 getViewHorizontalDragRange(View child):用我蹩腳的英語翻譯一下是‘返回的是子view在水平方向上可移動的大小,以畫素為單位,返回0的時候表示水平方向上不能拖動’

3、clampViewPositionHorizontal(View child, int left, int dx):在這裡可以對邊界進行檢查,left和dx分別代表即將移動到的位置

4、onViewPositionChanged(View changedView, int left, int top,
int dx, int dy):當要捕獲view,由於拖曳或者設定而發生位置變更時回撥

它的基本用法是:

    public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
        super
(context, attrs, defStyle); init(); } private void init() { viewDragHelper = ViewDragHelper.create(this, callback); } public boolean onInterceptTouchEvent(MotionEvent ev) { boolean result = viewDragHelper.shouldInterceptTouchEvent(ev); } public boolean
onTouchEvent(MotionEvent event) { viewDragHelper.processTouchEvent(event); return true; }

1)、在構造方法中建立
2)、在onInterceptTouchEvent 中判斷是否攔截
3 )、 在 onTouchEvent出來事件

好了 最不好理解的已經搞定了。接下來看看具體實現:

首先看佈局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <scrollviewgroup.lly.com.swiplayout.SwipeLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <!-- delete區域的佈局 -->
        <include layout="@layout/layout_delete" />
        <!-- item內容的佈局 -->
        <include layout="@layout/layout_content" />
    </scrollviewgroup.lly.com.swiplayout.SwipeLayout>

</LinearLayout>

這個沒什麼好說的,一個自定義viewgroup包含兩個子控制元件。
接著看看SwipeLayout是怎麼實現的:

 @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        deleteView = getChildAt(0);
        contentView = getChildAt(1);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        deleteHeight = deleteView.getMeasuredHeight();
        deleteWidth = deleteView.getMeasuredWidth();
        contentWidth = contentView.getMeasuredWidth();
        screenWidth = getWidth();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        // super.onLayout(changed, left, top, right, bottom);
        deleteView.layout(screenWidth - deleteWidth, 0, (screenWidth - deleteWidth)
                + deleteWidth, deleteHeight);
        contentView.layout(0, 0, contentWidth, deleteHeight);
    }

上面程式碼進行了一些初始化的操作,重點看看onlayout裡面的,我們繼承的是framelayout 這裡先畫出來 deleteView並讓他在右邊,然後在上面改了一層contentView,這樣顯示的時候只會顯示contentView。

接下來看ontouch方法

public boolean onTouchEvent(MotionEvent event) {
        //如果當前有開啟的,則下面的邏輯不能執行
        if(!SwipeLayoutManager.getInstance().isShouldSwipe(this)){
            requestDisallowInterceptTouchEvent(true);
            return true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //1.獲取x和y方向移動的距離
                float moveX = event.getX();
                float moveY = event.getY();
                float delatX = moveX - downX;//x方向移動的距離
                float delatY = moveY - downY;//y方向移動的距離
                if(Math.abs(delatX)>Math.abs(delatY)){
                    //表示移動是偏向於水平方向,那麼應該SwipeLayout應該處理,請求父view不要攔截
                    requestDisallowInterceptTouchEvent(true);
                }
                //更新downX,downY
                downX = moveX;
                downY = moveY;
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        viewDragHelper.processTouchEvent(event);
        return true;
    }

上面主要就是對事件衝突的處理,當是水平移動的時候就請求父檢視不要攔截。

接下來來重點就來了

 private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==contentView;
        }
        @Override
        public int getViewHorizontalDragRange(View child) {
            return deleteWidth;
        }
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(child==contentView){
                if(left>0)left = 0;
                if(left<-deleteWidth)left = -deleteWidth;
            }
            return left;
        }
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                                          int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            //判斷開和關閉的邏輯
            if(contentView.getLeft()==0 && currentState!=SwipeState.Close){
                //說明應該將state更改為關閉
                currentState = SwipeState.Close;

                //回撥介面關閉的方法
                if(listener!=null){
                    listener.onClose(getTag());
                }

                //說明當前的SwipeLayout已經關閉,需要讓Manager清空一下
                SwipeLayoutManager.getInstance().clearCurrentLayout();
            }else if (contentView.getLeft()==-deleteWidth && currentState!=SwipeState.Open) {
                //說明應該將state更改為開
                currentState = SwipeState.Open;

                //回撥介面開啟的方法
                if(listener!=null){
                    listener.onOpen(getTag());
                }
                //當前的Swipelayout已經開啟,需要讓Manager記錄一下下
                SwipeLayoutManager.getInstance().setSwipeLayout(SwipeLayout.this);
            }
        }
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if(contentView.getLeft()<-deleteWidth/2){
                //應該開啟
                open();
            }else {
                //應該關閉
                close();
            }
        }
    };

上面這段程式碼裡面的方法一開始我們都說過了,在來看下
在tryCaptureView中我們讓 contentView可以滑動,在getViewHorizontalDragRange中確定滑動範圍是deleteWidth,在clampViewPositionHorizontal中對邊界進行了下限制,在onViewPositionChanged中進行狀態的更新,
最後在手指擡起的時候讓view自動回滾,

 /**
     * 開啟的方法
     */
    public void open() {
        viewDragHelper.smoothSlideViewTo(contentView,-deleteWidth,contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
    }
    /**
     * 關閉的方法
     */
    public void close() {
        viewDragHelper.smoothSlideViewTo(contentView,0,contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
    };
    public void computeScroll() {
        if(viewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

這裡注意一定要重寫computeScroll方法,不然滑動效果動一下就不動了。

至此這個自定義framelayout就完成了

但是發現一個問題,我們在已經滑動出來的view中上下滑動時,這個view的deleteView還是顯示狀態,所以還要在activity中處理一下:

  recyView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if(dy>0 || dy<0){
                    SwipeLayoutManager.getInstance().closeCurrentLayout();
                }
            }
        });

當這個RecyclerView是上下滑動時,讓子view復位。
收工。
ps:本來是在eclipse中listview中實現的,但是想想google都已經不支援eclipse了,而listview也快被RecyclerView代替了,所以最後還是切換到android studio,用RecyclerView實現了一套。

原始碼地址