在前面的文章中已經介紹了側欄字母#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上面給個好評也行啊!也不枉費我忙活了這些時間了。謝謝!