吹牛皮
忙里偷閑在研究自定義View這一塊的東西,單純的使用觸摸事件加攔截事件等等的側(cè)滑功能還是寫過伏嗜,還沒用過
ViewDragHelper
來完成這個功能垮斯,所以就嘗試一下草戈!
一般來講坪创,如果要完成一個具有拖拽側(cè)滑的功能就必需要處理各種事件炕婶,比如onInterceptTouchEvent
和OnTouchEvent
,處理起來也不是很得心應(yīng)手莱预,出各種亂子的可能性都有柠掂!這個時候可以使用ViewDragHelper
來輔助我們完成這些操作,Google用ViewDragHelper
封裝了對onInterceptTouchEvent
和OnTouchEvent
的處理依沮,也就是說Google已經(jīng)替我們寫好了邏輯涯贞,我們只需要設(shè)定好條條框框(比如邊界判斷等)就行了。
一悉抵、不扯有的沒的 回歸正題
UI什么的都是臨時搭的肩狂,很丑啊摘完,但是很溫柔姥饰!
↓↓↓先上效果圖↓↓↓
效果圖看完了繼續(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;//移動距離 小于這個距離就不觸發(fā)移動控件 恢復(fù)到當前位置
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ù)越大越敏感列粪。第一個參數(shù)為this审磁,表示該類生成的對象,
// 他是ViewDragHelper的拖動處理對象岂座,必須為ViewGroup态蒂。
mDragHelper = ViewDragHelper.create(this, 1.0f, new CallBack());
//48dp 是一個距離,表示滑動的時候费什,手的移動要大于這個距離才開始移動控件钾恢。如果小于這個距離就不觸發(fā)移動控件,
// 如viewpager就是用這個距離來判斷用戶是否翻頁
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("必須有兩個子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)該攔截當前的事件鸳址。
*
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
/**
* onTouchEvent中通過mDragger.processTouchEvent(event)處理事件瘩蚪。
*
* @param event
* @return true 時間已消費(交給了mDragHelper)不往下傳遞
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
/**
* 這個計算滑動的函數(shù)computeScroll(),就是用于判斷滾動是否完成的稿黍。
* 在computeScroll方法中判斷smoothSlideViewTo觸發(fā)的continueSettling(boolean)的返回值疹瘦,來動態(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在所屬方向上移動的位置(這里是水平方向),child為拖拽的子View巡球,left為子view應(yīng)該到達的x坐標言沐,dx為挪動差值
//return left
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
Log.e(TAG + "clampViewPositionHorizontal", left + "");
//以下兩個判斷是防止越界(部分View被遮住)
if (left > getPaddingLeft()) {//向右滑動時 超過了paddingLeft都返回這個值(保持原位)
return getPaddingLeft();
}
if (left < getPaddingLeft() - mDragDistance) {//向左滑動 整個隱藏的View都滑出來了 超過了getPaddingLeft() - mDragDistance都返回這個值(保持原位)
return getPaddingLeft() - mDragDistance;
}
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return getPaddingTop();
}
//返回拖拽子View在相應(yīng)方向上可以被拖動的最遠距離酣栈,默認為0
@Override
public int getViewHorizontalDragRange(View child) {
Log.e(TAG + "getViewHorizontalDragRange", mDragDistance + "");
return mDragDistance;
}
//當前拖拽的view松手或者ACTION_CANCEL時調(diào)用险胰,xvel、yvel為離開屏幕時的速率
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//getPaddingLeft() - mDragView.getLeft() → 控件不動時為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位置變化時回調(diào)矿筝,changedView為位置變化的view鸯乃,left、top變化后的x跋涣、y坐標缨睡,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方法某個View自動滾動到指定的位置陈辱,如果這個方法返回true奖年,那么在接下來動畫移動的每一幀中都會回調(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)閉滑動
*/
public void close(){
if (mState == STATE_OPEN){
smoothSlideHide();
ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
}
}
}
好了沛贪,自定義view完成了陋守,代碼中注釋已經(jīng)一目了然了,都是用比較淺顯的話來表達(片面)利赋,只能說話糙理不糙水评,看得懂才是王道。
二媚送、大致的UI布局
創(chuàng)建在res\layout
文件夾下創(chuàng)建一個xml文件中燥,命名為item_swipelayout.xml。首先側(cè)滑我們不難看出只分為兩個部分塘偎,第一部分為內(nèi)容區(qū)域(可視部分)疗涉,第二部分為菜單區(qū)域(隱藏部分)拿霉。
所以自定view類SwipeLayout1
中就要求在布局時要包含兩個子view(即兩個部分),具體布局如下:
<?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>
到這里咱扣,自定義效果就完成了绽淘,只需新建一個activty把布局文件item_swipelayout.xml
加載一下就行了。好吧闹伪,還是寫一下沪铭,新建一個activity命名為SwipeLayoutActivity,如下:
public class SwipeLayoutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.item_swipelayout);
}
}
簡單粗暴的就可以運行了偏瓤,如果你要調(diào)用自定義View里面的關(guān)閉滑動的方法就需要進行findViewById的操作了伦意,然后調(diào)用即可。
還可以進行很多擴展硼补,比如應(yīng)用到ListView中驮肉,這一部分后面應(yīng)該會加上去,應(yīng)該在自定義view中加回調(diào)方法就行
不能做伸手黨已骇,所借鑒大神的地址:https://github.com/mzw1004
寫完离钝,可以開始打坐了!啦啦啦啦啦啦啦啦啦啦啦啦褪储,簡單粗暴卵渴。