Android 側欄A-Z的快速滑動搜索(二)

在前面的文章中已經介紹了側欄字母#A-Z以及搜索框的實現。
這裏寫圖片描述
這次主要是來實現以下側滑的功能,側滑也是很多應用裏面都有的,我們所熟悉的QQ裏面的消息條目就是使用的側滑功能。下面我們就來說說側滑功能的實現吧。
側滑刪除當然也是離不了自定義控件的而且還要有滑動的動畫出現,所以我們會使用到ViewDragHelper,使用是需要以下幾個步驟

 1.使用靜態方法來構ViewDragHelper,需要傳入一個ViewDragHelper.Callback對象.
 這個一般就是在我們初始化的時候使用靜態方法構造ViewDragHelper,其中需要傳入一個ViewDragHelper.Callback回調對象.
 2.重寫onInterceptTouchEvent和onTouchEvent回調ViewDragHelper中對應方法.
 3.在ViewDragHelper.Callback中對視圖做操作.
 4.使用ViewDragHelper.smoothSlideViewTo()方法平滑滾動.
 5.自定義一些交互邏輯的自由實現.

“`
package lyx.robert.quicksearch.view;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import lyx.robert.quicksearch.utils.SwipeManager;

public class SwipeLayout extends FrameLayout {

private View contentView;// 默認顯示內容的view
private View slipView;// 側滑控件的view
private int slipHeight;//  側滑控件的高度
private int slipWidth;// 側滑控件的寬度
private int contentWidth;// 默認顯示內容的寬度
private ViewDragHelper mDrag;

public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

public SwipeLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public SwipeLayout(Context context) {
    super(context);
    init();
}

enum SwipeState{
    Open,Close;
}

private SwipeState currentState = SwipeState.Close;//默認是關閉狀態

private void init() {
    //使用靜態方法構造ViewDragHelper,其中需要傳入一個ViewDragHelper.Callback回調對象.
    mDrag = ViewDragHelper.create(this, callback);
}

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

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    slipHeight = slipView.getMeasuredHeight();
    slipWidth = slipView.getMeasuredWidth();
    contentWidth = contentView.getMeasuredWidth();
}

@Override
protected void onLayout(boolean changed, int left, int top, int right,
        int bottom) {
    contentView.layout(0, 0, contentWidth, slipHeight);
    slipView.layout(contentView.getRight(), 0, contentView.getRight()
            + slipWidth, slipHeight);
}
//重寫此方法回調ViewDragHelper中對應的方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean result = mDrag.shouldInterceptTouchEvent(ev);

    //如果當前有打開的,則需要直接攔截,交給onTouch處理
    if(!SwipeManager.getInstance().isSwipe(this)){
        //先關閉已經打開的layout
        SwipeManager.getInstance().closeCurrentLayout();

        result = true;
    }

    return result;
}

private float downX,downY;
//重寫此方法回調ViewDragHelper中對應的方法.
@Override
public boolean onTouchEvent(MotionEvent event) {
    //如果當前有打開的,調用requestDisallowInterceptTouchEvent進行攔截,觸摸事件的邏輯將不會執行
    if(!SwipeManager.getInstance().isSwipe(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應該處理,請求listview不要攔截
            requestDisallowInterceptTouchEvent(true);
        }
        //更新downX,downY
        downX = moveX;
        downY = moveY;
        break;
    case MotionEvent.ACTION_UP:

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

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
    //返回值就是當前捕捉到的也就是你當前拖拽的某個子View
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        return child==contentView||child==slipView;
    }
    //返回值是可以水平拖拽的範圍
    @Override
    public int getViewHorizontalDragRange(View child) {
        return slipWidth;
    }
    //手指觸摸移動時實時回調, left表示要移動到的x位置,dx表示移動的距離
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        if(child==contentView){
            if(left>0)left = 0;
            if(left<-slipWidth)left = -slipWidth;
        }else if (child==slipView) {
            if(left>contentWidth)left = contentWidth;
            if(left<(contentWidth-slipWidth))left = contentWidth-slipWidth;
        }
        return left;
    }
    //拖拽的子View完全挪出屏幕則防止過度繪製
    @Override
    public void onViewPositionChanged(View changedView, int left, int top,
            int dx, int dy) {
        super.onViewPositionChanged(changedView, left, top, dx, dy);
        if(changedView==contentView){
            //手動移動slipView
            slipView.layout(slipView.getLeft()+dx,slipView.getTop()+dy,
                    slipView.getRight()+dx, slipView.getBottom()+dy);
        }else if (slipView==changedView) {
            //手動移動contentView
            contentView.layout(contentView.getLeft()+dx,contentView.getTop()+dy,
                    contentView.getRight()+dx, contentView.getBottom()+dy);
        }

        //判斷開和關閉的邏輯
        if(contentView.getLeft()==0 && currentState!=SwipeState.Close){
            //說明應該將state更改爲關閉
            currentState = SwipeState.Close;

            //回調接口關閉的方法
            if(swipeListener!=null){
                swipeListener.onClose(getTag());
            }

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

            //回調接口打開的方法
            if(swipeListener!=null){
                swipeListener.onOpen(getTag());
            }
            //當前的Swipelayout已經打開,需要讓Manager記錄一下下
            SwipeManager.getInstance().setSwipeLayout(SwipeLayout.this);
        }
    }
    //手指釋放時回調此方法
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
        if(contentView.getLeft()<-slipWidth/2){
            //如果滑動的內容大於側滑內容的一半應當打開
            open();
        }else {
            //否則就是關閉
            close();
        }
    }
};
/**
 * 打開的方法
 */
public void open() {
    mDrag.smoothSlideViewTo(contentView,-slipWidth,contentView.getTop());
    ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
/**
 * 關閉的方法
 */
public void close() {
    mDrag.smoothSlideViewTo(contentView,0,contentView.getTop());
    ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
public void computeScroll() {
    if(mDrag.continueSettling(true)){
        ViewCompat.postInvalidateOnAnimation(this);
    }
}

private SwipeListener swipeListener;
public void setOnSwipeListener(SwipeListener listener){
    this.swipeListener = listener;
}

public interface SwipeListener{
    void onOpen(Object obj);
    void onClose(Object obj);
}

}
回true(因爲ACTION_DOWN時如果子View沒有消費事件,我們需要在onTouchEvent()中返回true,否則收不到後續的事件,從而不會產生拖動等效果)。
在代碼中我們會發現requestDisallowInterceptTouchEvent(true),他的作用是什麼呢?Android事件機制是從父View傳向子View的,可以去檢測你當前子View是不是在有可滑動控件等,決定事件是否攔截,但是這個麻煩,而且並不能解決所有的問題(必須檢測觸摸點是否在這個控件上面),其實有比較簡單的方法,在你嵌套的控件中注入ViewPager實例(調用控件的getParent()方法),然後在onTouchEvent,onInterceptTouchEvent,dispatchTouchEvent裏面告訴父View,也就是ViewPager不要攔截該控件上的觸摸事件。調用該方法,一旦底層View收到touch的action後調用這個方法, 那麼父層View就不會再調用onInterceptTouchEvent了,也無法截獲以後的action。
onFinishInflate()的作用相當於我們一般使用View的流程是在onCreate中使用setContentView來設置要顯示Layout文件或直接創建一個View,在當設置了ContentView之後系統會對這個View進行解析,然後回調當前視圖View中的onFinishInflate方法。只有解析了這個View我們才能在這個View容器中獲取到擁有Id的組件,同樣因爲系統解析完View之後纔會調用onFinishInflate方法,所以我們自定義組件時可以onFinishInflate方法中獲取指定子View的引用。
模糊搜索懸浮提示點擊前往Android 側欄A-Z的快速滑動搜索(三)

點擊前往csdn下載源碼

點擊前往GitHub下載源碼

帥哥/美女,如果對您有幫助在GitHub上面點下star唄哦!再csdn上面給個好評也行啊!也不枉費我忙活了這些時間了。謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章