吹牛皮
忙里偷闲在研究自定义View这一块的东西,单纯的使用触摸事件加拦截事件等等的侧滑功能还是写过,还没用过
ViewDragHelper
来完成这个功能,所以就尝试一下!
一般来讲,如果要完成一个具有拖拽侧滑的功能就必需要处理各种事件,比如onInterceptTouchEvent
和OnTouchEvent
,处理起来也不是很得心应手,出各种乱子的可能性都有!这个时候可以使用ViewDragHelper
来辅助我们完成这些操作,Google用ViewDragHelper
封装了对onInterceptTouchEvent
和OnTouchEvent
的处理,也就是说Google已经替我们写好了逻辑,我们只需要设定好条条框框(比如边界判断等)就行了。
一、不扯有的没的 回归正题
UI什么的都是临时搭的,很丑啊,但是很温柔!
↓↓↓先上效果图↓↓↓
效果图看完了继续往下看。
二、自定义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;//移动距离 小于这个距离就不触发移动控件 恢复到当前位置
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是敏感度参数参数越大越敏感。第一个参数为this,表示该类生成的对象,
// 他是ViewDragHelper的拖动处理对象,必须为ViewGroup。
mDragHelper = ViewDragHelper.create(this, 1.0f, new CallBack());
//48dp 是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,
// 如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)来决定我们是否应该拦截当前的事件。
*
* @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;
}
/**
* 这个计算滑动的函数computeScroll(),就是用于判断滚动是否完成的。
* 在computeScroll方法中判断smoothSlideViewTo触发的continueSettling(boolean)的返回值,来动态刷新界面
*/
@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应该到达的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在相应方向上可以被拖动的最远距离,默认为0
@Override
public int getViewHorizontalDragRange(View child) {
Log.e(TAG + "getViewHorizontalDragRange", mDragDistance + "");
return mDragDistance;
}
//当前拖拽的view松手或者ACTION_CANCEL时调用,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位置变化时回调,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,那么在接下来动画移动的每一帧中都会回调continueSettling(boolean)方法,直到结束
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;
}
/**
* 关闭滑动
*/
public void close(){
if (mState == STATE_OPEN){
smoothSlideHide();
ViewCompat.postInvalidateOnAnimation(SwipeLayout1.this);
}
}
}
好了,自定义view完成了,代码中注释已经一目了然了,都是用比较浅显的话来表达(片面),只能说话糙理不糙,看得懂才是王道。
二、大致的UI布局
创建在res\layout
文件夹下创建一个xml文件,命名为item_swipelayout.xml。首先侧滑我们不难看出只分为两个部分,第一部分为内容区域(可视部分),第二部分为菜单区域(隐藏部分)。
所以自定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);
}
}
简单粗暴的就可以运行了,如果你要调用自定义View里面的关闭滑动的方法就需要进行findViewById的操作了,然后调用即可。
还可以进行很多扩展,比如应用到ListView中,这一部分后面应该会加上去,应该在自定义view中加回调方法就行
不能做伸手党,所借鉴大神的地址:https://github.com/mzw1004
写完,可以开始打坐了!啦啦啦啦啦啦啦啦啦啦啦啦,简单粗暴。