吹牛皮
忙里偷閑在研究自定義View這一塊的東西,單純的使用觸摸事件加攔截事件等等的側(cè)滑功能還是寫過,還沒用過
ViewDragHelper來完成這個(gè)功能,所以就嘗試一下!
一般來講,如果要完成一個(gè)具有拖拽側(cè)滑的功能就必需要處理各種事件,比如onInterceptTouchEvent 和OnTouchEvent,處理起來也不是很得心應(yīng)手,出各種亂子的可能性都有!這個(gè)時(shí)候可以使用ViewDragHelper來輔助我們完成這些操作,Google用ViewDragHelper 封裝了對(duì)onInterceptTouchEvent 和OnTouchEvent的處理,也就是說Google已經(jīng)替我們寫好了邏輯,我們只需要設(shè)定好條條框框(比如邊界判斷等)就行了。
一、不扯有的沒的 回歸正題
UI什么的都是臨時(shí)搭的,很丑啊,但是很溫柔!
↓↓↓先上效果圖↓↓↓

效果圖看完了繼續(xù)往下看。
二、自定義View
/**
* Created by Leogh on 2017/8/25.
*/
public class SwipeLayout1 extends LinearLayout {
private ViewDragHelper mDragHelper = null;
private View mDragView;
private View mHideView;
private int mDragSlop;//移動(dòng)距離 小于這個(gè)距離就不觸發(fā)移動(dòng)控件 恢復(fù)到當(dāng)前位置
private int mWidth;
private int mHeight;
private int mDragDistance;
private final int STATE_CLOSE = 1001;
private final int STATE_OPEN = 1002;
private int mState = STATE_CLOSE;
public SwipeLayout1(Context context) {
this(context, null);
}
public SwipeLayout1(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeLayout1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
//其中1.0f是敏感度參數(shù)參數(shù)越大越敏感。第一個(gè)參數(shù)為this,表示該類生成的對(duì)象,
// 他是ViewDragHelper的拖動(dòng)處理對(duì)象,必須為ViewGroup。
mDragHelper = ViewDragHelper.create(this, 1.0f, new CallBack());
//48dp 是一個(gè)距離,表示滑動(dòng)的時(shí)候,手的移動(dòng)要大于這個(gè)距離才開始移動(dòng)控件。如果小于這個(gè)距離就不觸發(fā)移動(dòng)控件,
// 如viewpager就是用這個(gè)距離來判斷用戶是否翻頁(yè)
mDragSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(ViewConfiguration.get(getContext()));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mDragView.layout(getPaddingLeft(), getPaddingTop(), mWidth - getPaddingRight(), mHeight - getPaddingBottom());
mHideView.layout(mWidth - getPaddingRight(), getPaddingTop(), mWidth - getPaddingRight() + mDragDistance, mHeight - getPaddingBottom());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
try {
mDragView = getChildAt(0);
mHideView = getChildAt(1);
} catch (Exception e) {
throw new NullPointerException("必須有兩個(gè)子view");
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mHeight = h;
mDragDistance = mHideView.getMeasuredWidth();
}
/**
* onInterceptTouchEvent中通過使用mDragger.shouldInterceptTouchEvent(event)來決定我們是否應(yīng)該攔截當(dāng)前的事件。
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
/**
* onTouchEvent中通過mDragger.processTouchEvent(event)處理事件。
*
* @param event
* @return true 時(shí)間已消費(fèi)(交給了mDragHelper)不往下傳遞
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
/**
* 這個(gè)計(jì)算滑動(dòng)的函數(shù)computeScroll(),就是用于判斷滾動(dòng)是否完成的。
* 在computeScroll方法中判斷smoothSlideViewTo觸發(fā)的continueSettling(boolean)的返回值,來動(dòng)態(tài)刷新界面
*/
@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
// postInvalidate();
}
}
class CallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == mDragView;
}
//拖拽的子View在所屬方向上移動(dòng)的位置(這里是水平方向),child為拖拽的子View,left為子view應(yīng)該到達(dá)的x坐標(biāo),dx為挪動(dòng)差值
//return left
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
Log.e(TAG + "clampViewPositionHorizontal", left + "");
//以下兩個(gè)判斷是防止越界(部分View被遮?。? if (left > getPaddingLeft()) {//向右滑動(dòng)時(shí) 超過了paddingLeft都返回這個(gè)值(保持原位)
return getPaddingLeft();
}
if (left < getPaddingLeft() - mDragDistance) {//向左滑動(dòng) 整個(gè)隱藏的View都滑出來了 超過了getPaddingLeft() - mDragDistance都返回這個(gè)值(保持原位)
return getPaddingLeft() - mDragDistance;
}
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return getPaddingTop();
}
//返回拖拽子View在相應(yīng)方向上可以被拖動(dòng)的最遠(yuǎn)距離,默認(rèn)為0
@Override
public int getViewHorizontalDragRange(View child) {
Log.e(TAG + "getViewHorizontalDragRange", mDragDistance + "");
return mDragDistance;
}
//當(dāng)前拖拽的view松手或者ACTION_CANCEL時(shí)調(diào)用,xvel、yvel為離開屏幕時(shí)的速率
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//getPaddingLeft() - mDragView.getLeft() → 控件不動(dòng)時(shí)為0
int getPaddingLeft = getPaddingLeft();
int getmDragViewLeft = mDragView.getLeft();
int temp = getPaddingLeft() - mDragView.getLeft();
int tempdragSlop = mDragSlop;
if (getPaddingLeft() - mDragView.getLeft() < mDragSlop) {//最終位置的判斷
smoothSlideHide();
} else {
smoothSlideOpen();
}
ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
// postInvalidate();
}
//被拖拽的View位置變化時(shí)回調(diào),changedView為位置變化的view,left、top變化后的x、y坐標(biāo),dx、dy為新位置與舊位置的偏移量
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
Log.e(TAG + "onViewPositionChanged", dx + "");
mHideView.layout(mHideView.getLeft() + dx, mHideView.getTop(), mHideView.getRight() + dx, mHideView.getBottom());
ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
// postInvalidate();
}
}
private void smoothSlideHide(){
//smoothSlideViewTo方法某個(gè)View自動(dòng)滾動(dòng)到指定的位置,如果這個(gè)方法返回true,那么在接下來動(dòng)畫移動(dòng)的每一幀中都會(huì)回調(diào)continueSettling(boolean)方法,直到結(jié)束
mDragHelper.smoothSlideViewTo(getHideView(), mWidth - getPaddingRight(), getPaddingTop());
mDragHelper.smoothSlideViewTo(getDragView(), getPaddingLeft(), getPaddingTop());
mState = STATE_CLOSE;
}
private void smoothSlideOpen(){
mDragHelper.smoothSlideViewTo(getHideView(), mWidth - getPaddingRight() - mDragDistance, getPaddingTop());
mDragHelper.smoothSlideViewTo(getDragView(), getPaddingLeft() - mDragDistance, getPaddingTop());
mState = STATE_OPEN;
}
public View getDragView() {
if (getChildCount() == 0) return null;
return getChildAt(0);
}
public View getHideView() {
if (getChildCount() == 1) return null;
return getChildAt(1);
}
private void setState(int state){
this.mState = state;
}
private int getState(){
return mState;
}
/**
* 關(guān)閉滑動(dòng)
*/
public void close(){
if (mState == STATE_OPEN){
smoothSlideHide();
ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
}
}
}
好了,自定義view完成了,代碼中注釋已經(jīng)一目了然了,都是用比較淺顯的話來表達(dá)(片面),只能說話糙理不糙,看得懂才是王道。
二、大致的UI布局
創(chuàng)建在res\layout文件夾下創(chuàng)建一個(gè)xml文件,命名為item_swipelayout.xml。首先側(cè)滑我們不難看出只分為兩個(gè)部分,第一部分為內(nèi)容區(qū)域(可視部分),第二部分為菜單區(qū)域(隱藏部分)。
所以自定view類SwipeLayout1中就要求在布局時(shí)要包含兩個(gè)子view(即兩個(gè)部分),具體布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.sobergh.soberghalltest.itemslideview.SwipeLayout1
android:id="@+id/sl"
android:layout_width="match_parent"
android:layout_height="70dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_bright"
android:gravity="center_vertical"
android:text="老臘肉老臘肉老臘肉老臘肉老臘肉老臘肉"/>
<LinearLayout
android:layout_width="80dp"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_top"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_orange_light"
android:gravity="center"
android:text="置頂"/>
<TextView
android:id="@+id/tv_delete"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/holo_green_light"
android:gravity="center"
android:text="刪除"/>
</LinearLayout>
</com.sobergh.soberghalltest.itemslideview.SwipeLayout1>
</RelativeLayout>
到這里,自定義效果就完成了,只需新建一個(gè)activty把布局文件item_swipelayout.xml加載一下就行了。好吧,還是寫一下,新建一個(gè)activity命名為SwipeLayoutActivity,如下:
public class SwipeLayoutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.item_swipelayout);
}
}
簡(jiǎn)單粗暴的就可以運(yùn)行了,如果你要調(diào)用自定義View里面的關(guān)閉滑動(dòng)的方法就需要進(jìn)行findViewById的操作了,然后調(diào)用即可。
還可以進(jìn)行很多擴(kuò)展,比如應(yīng)用到ListView中,這一部分后面應(yīng)該會(huì)加上去,應(yīng)該在自定義view中加回調(diào)方法就行
不能做伸手黨,所借鑒大神的地址:https://github.com/mzw1004
寫完,可以開始打坐了!啦啦啦啦啦啦啦啦啦啦啦啦,簡(jiǎn)單粗暴。