——-不知道说些啥..
嗯,前面我们仿着做了QQ的侧拉菜单,现在来做做侧拉删除,复习复习下我们ViewDragHelper的使用,先来看下效果吧
右边的是整合上次的侧滑菜单效果,没看过想了解的朋友可以去看看侧滑菜单的实现http://blog.csdn.net/z8z87878/article/details/51731221
额,控件缓慢拖动判断距离是否打开或关闭也做了,后面再录着看效果吧,好了,现在开始我们的自定义侧拉删除吧.其实这个和上次那个侧拉滑动还是挺像的是不是,其实是这样的,像这种自定义拖动容器,就是那几个类那几个方法,所以我们在熟悉这几个类和方法的情况下,其实自定义这种拖动容器是并不难的.开始自定义前,我们的第一个问题就是继承那个容器控件,如果自己继承ViewGroup的话,是挺不好的,它要我们写更多的代码,所以我们应该去继承那些常用的容器控件,其实也没几个是吧.我一开始根据这个控件的特性想的是继承LineLayout,单个条目是没问题的,但是把它整合到ListView的时候出问题了,不能显示,从来没碰过这种情况,我当时就懵了….后来试着继承FrameLayout.可以.所以我们这个控件继承FrameLayout,这为什么继承lineLayout然后ListView不显示,其中有多少不为人知的秘密……请你们探索着然后告诉我….
我们继承frameLayout要实现上面的效果,所以我们要重写onLayout布局方法
@Override
protected void onFinishInflate() { //视图解析完成后调用,这时候就能拿到我们的孩子对象了
super.onFinishInflate();
mRightView = getChildAt(0); //右边选项界面
mMainView = getChildAt(1); //主面板
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { //测量完成后调用,这时我们可以拿到view的大小
super.onSizeChanged(w, h, oldw, oldh);
mMainWidth = mMainView.getMeasuredWidth();
mMainHeight = mMainView.getMeasuredHeight();
mRightWidth = mRightView.getMeasuredWidth();
mRange = mRightWidth; //拖动范围
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mRightView.layout(mMainWidth,0,mMainWidth + mRightWidth,mMainHeight);
}
这样就实现我们图片中的效果了,静态初始位置布置妥当.我们就可以用我们的ViewDragHelper了来帮助我们处理拖动事件了,这前面侧滑菜单已经讲过了,这里提下大致过程,首选我们得到它的对象ViewDragHelper create(ViewGroup forParent, Callback cb)要写一个类继承它的内部类ViewDragHelper.CallBack, 其中tryCaptureView(View child, int pointerId)是抽象方法,我们必须实现,想处理拖动事件返回true,因为这是水平拖动,所以我们还要重写clampViewPositionHorizontal(View child, int left, int dx)它默认返回0,所以我们要判断left它给我们的建议值,我们要判断它的范围,我们再返回自己计算的left,然后,相应的还要竖直的,我们用不到不重写
/** * @param child 拖动那个孩子 * @param left 拖动建议值,view根据返回的建议值拖动,相对于左位负,相对于右为正,是view相对物理窗口左边的位置 * @param dx 变化值 * @return 默认返回0, 即不拖动 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mMainView) {
if (left > 0) {
left = 0;
} else if (left < -mRange) {
left = -mRange; //拖动范围
}
} else if (child == mRightView) {
if (left < mMainWidth - mRightWidth) {
left = mMainWidth - mRightWidth;
} else if (left > mMainWidth) {
left = mRange;
}
}
return left;
}
还要个方法叫onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 重写这个方法可以帮助我们在一个view拖动的时候,我们动态layout另一个view达到跟着动的效果,也可以在这设置回调之类的东西
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == mMainView) {
mRightView.layout(mMainWidth + left, mRightView.getTop(), mMainWidth + left + mRightWidth, mRightView
.getBottom());
} else if (changedView == mRightView) {
mMainView.layout(mRightView.getLeft() - mMainWidth, 0, mRightView.getLeft(),
mMainHeight);
}
if (mOnDraeListener != null) {
int mManLeft = mMainView.getLeft();
if (!isOpen && mManLeft == - mRightWidth){ //打开状态,原来是打开的就不用重新回调了
mOnDraeListener.onOpen(mDragLayout);
isOpen = true;
}else if (isOpen && mManLeft == 0){
mOnDraeListener.onclose(mDragLayout);
isOpen = false;
isStartOpen = false;
}else{
mOnDraeListener.onDrag(changedView, left); //接口回调
if (!isOpen && !isStartOpen){ //只有在关闭的情况下才是要开始打开了
mOnDraeListener.onStartOpen(mDragLayout);
isStartOpen = true;
}
}
}
invalidate();
}
接口回调,用来处理比如滑动条目的时候关闭其它已经打开的条目之类的
public interface onDraeListener {
void onDrag(View view, int left);
void onOpen(DragLayout dragLayout); //打开了
void onclose(DragLayout dragLayout); //关闭了
void onStartOpen(DragLayout dragLayout); //开始打开,不一定打开
}
private onDraeListener mOnDraeListener; //引用
public void setOnDraeListener(onDraeListener onDraeListener) { //由外部传个名字
mOnDraeListener = onDraeListener;
}
还有一个就是onViewReleased(View releasedChild, float xvel, float yvel)我们在这个方法里根据画出的距离或者滑的速度方向,判断我们是要打开,还是关闭.
// xvel //水平速度
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel < 0) { //认为松手后还有左的,可以叫惯性速度么...就打开
open();
} else if (xvel == 0) { //不确定是关闭还是打开状态
if (isOpen) {
if (mMainView.getLeft() > -mRange / mRightChildeCount * (mRightChildeCount - 1)) {
close(); //打开状态,即向右滑超过三分之一关闭
} else {
open();
}
} else { //关闭状态跟上面一样向左滑
if (mMainView.getLeft() > -mRange / mRightChildeCount) {
close();
} else {
open();
}
}
} else if (xvel > 0) { //打开状态即向右滑
close();
}
}
}
public void open() {
mViewDragHelper.smoothSlideViewTo(mMainView, -mRange, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
public void close() {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
这里讲下自定义属性mRightChildCount
public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragLayout, defStyleAttr, 0);
mRightChildeCount = typedArray.getInt(R.styleable.DragLayout_rightChildCount, -1);
if (mRightChildeCount < 2) {
throw new IllegalArgumentException("DragLayout右面板孩子数最少为两个");
}
mViewDragHelper = ViewDragHelper.create(this, new MyCallBack());
mDragLayout = this;
}
其实我这里是没有必要自定义属性的,但我代码已经上传了,有兴趣的自己把自定义属性去了吧,既然我当初做了,就教不会的朋友一下怎么自定义属性的,其实我一开始也是不会的,但我们可以想想,那些控件的属性是怎么定义的呢,所以我到LineLayout的构造函数中去看了下,它是这样的
没见过猪跑,你吃过猪肉吧.属性是通过attrs来的,所以我们去找找SDK的attrs文件看看,路径是SDK\platforms\android-23\data\res\values,打开,我们找到LineLayout,是这样的
<declare-styleable name="LinearLayout">
<!-- Should the layout be a column or a row? Use "horizontal" for a row, "vertical" for a column. The default is horizontal. -->
<attr name="orientation" />
<attr name="gravity" />
<!-- When set to false, prevents the layout from aligning its children's baselines. This attribute is particularly useful when the children use different values for gravity. The default value is true. -->
<attr name="baselineAligned" format="boolean" />
<!-- When a linear layout is part of another layout that is baseline aligned, it can specify which of its children to baseline align to (that is, which child TextView).-->
<attr name="baselineAlignedChildIndex" format="integer" min="0"/>
<!-- Defines the maximum weight sum. If unspecified, the sum is computed by adding the layout_weight of all of the children. This can be used for instance to give a single child 50% of the total available space by giving it a layout_weight of 0.5 and setting the weightSum to 1.0. -->
<attr name="weightSum" format="float" />
<!-- When set to true, all children with a weight will be considered having the minimum size of the largest child. If false, all children are measured normally. -->
<attr name="measureWithLargestChild" format="boolean" />
<!-- Drawable to use as a vertical divider between buttons. -->
<attr name="divider" />
<!-- Setting for which dividers to show. -->
<attr name="showDividers">
<flag name="none" value="0" />
<flag name="beginning" value="1" />
<flag name="middle" value="2" />
<flag name="end" value="4" />
</attr>
<!-- Size of padding on either end of a divider. -->
<attr name="dividerPadding" format="dimension" />
</declare-styleable>
所以我们写自己的attrs的一般格式为
<declare-styleable name="xxxxxx">
<attr name="xxx" format="xxx"/>
</declare-styleable>
即我是这样写的
<declare-styleable name="DragLayout">
<attr name="rightChildCount" format="integer"></attr>
</declare-styleable>
写好了这个类,我们还要把touch事件传给我们的ViewDragOpenHelper
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev); //交给我们的mViewDragHelper去判断该不该拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mViewDragHelper.processTouchEvent(event); //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
好吧,鼠标不好控制,刚开始的鼠标抬起带了点向左的速度所以打开了/没问题现在就可以整合到listView了,你会发现,打开后,滑右面板,向右滑动不了,为什么呢O(∩_∩)O~,因为和上一次侧拉一样,我们没有重写ViewDragHelper.CallBack中的这个方法
@Override
public int getViewHorizontalDragRange(View child) { //水平移动范围,我们最好重写这个方法!不然碰到listView拖不动
return mRange;
}
这个方法一定要重写!!!!!!!!如果是竖直方向的就重写竖直方向的,不然碰到listView你就等着哭吧,其实我一开始是写了的,昨天晚上我写好了还没整到ListView上,说不信邪看看能不能滑动,然后今天上午因为一开始继承的是LineLayout的,listView不显示就把我整懵了,然后就忘了,将近一个小时候看代码才发现…我真是作死小能手….重写后就没问题了,这里再说明下滑动关闭条目的处理,我们用一个ArrayList在回调onOpen的时候存储我们的打开的条目,然后close的时候移除它,在onStartOpen的时候遍历的List关闭里面的条目,并清空,在ListView的滑动也是遍历这个list关闭所有条目,并清空,详情可以下载下来看看,下载地址http://download.csdn.net/detail/z8z87878/9557318
这里我贴下这个控件的完整代码
import android.content.Context;
import android.content.res.TypedArray;
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 com.it.draglayout.R;
/** * Created by Root on 2016/6/22. */
public class DragLayout extends FrameLayout {
private View mMainView;
private View mRightView;
private int mMainWidth;
private int mRightWidth;
private ViewDragHelper mViewDragHelper;
private int mRange;
private int mMainHeight;
private boolean isOpen = false;
private int mRightChildeCount;
private boolean isStartOpen = false;
DragLayout mDragLayout;
public DragLayout(Context context) {
this(context, null);
}
public DragLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DragLayout, defStyleAttr, 0);
mRightChildeCount = typedArray.getInt(R.styleable.DragLayout_rightChildCount, -1);
if (mRightChildeCount < 2) {
throw new IllegalArgumentException("DragLayout右面板孩子数最少为两个");
}
mViewDragHelper = ViewDragHelper.create(this, new MyCallBack());
mDragLayout = this;
}
@Override
protected void onFinishInflate() { //视图解析完成后调用,这时候就能拿到我们的孩子对象了
super.onFinishInflate();
mRightView = getChildAt(0); //右边选项界面
mMainView = getChildAt(1); //主面板
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { //测量完成后调用,这时我们可以拿到view的大小
super.onSizeChanged(w, h, oldw, oldh);
mMainWidth = mMainView.getMeasuredWidth();
mMainHeight = mMainView.getMeasuredHeight();
mRightWidth = mRightView.getMeasuredWidth();
mRange = mRightWidth; //拖动范围
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mRightView.layout(mMainWidth,0,mMainWidth + mRightWidth,mMainHeight);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev); //交给我们的mViewDragHelper去判断该不该拦截
}
@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mViewDragHelper.processTouchEvent(event); //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
class MyCallBack extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) { //要求我们重写,返回true执行拖到事件,false不执行
return true;
}
@Override
public int getViewHorizontalDragRange(View child) { //水平移动范围,我们最好重写这个方法!不然碰到listView拖不动
return mRange;
}
/** * @param child 拖动那个孩子 * @param left 拖动建议值,view根据返回的建议值拖动,相对于左位负,相对于右为正,是view相对物理窗口左边的位置 * @param dx 变化值 * @return 默认返回0, 即不拖动 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mMainView) {
if (left > 0) {
left = 0;
} else if (left < -mRange) {
left = -mRange; //拖动范围
}
} else if (child == mRightView) {
if (left < mMainWidth - mRightWidth) {
left = mMainWidth - mRightWidth;
} else if (left > mMainWidth) {
left = mRange;
}
}
return left;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == mMainView) {
mRightView.layout(mMainWidth + left, mRightView.getTop(), mMainWidth + left + mRightWidth, mRightView
.getBottom());
} else if (changedView == mRightView) {
mMainView.layout(mRightView.getLeft() - mMainWidth, 0, mRightView.getLeft(),
mMainHeight);
}
if (mOnDraeListener != null) {
int mManLeft = mMainView.getLeft();
if (!isOpen && mManLeft == - mRightWidth){ //打开状态,原来是打开的就不用重新回调了
mOnDraeListener.onOpen(mDragLayout);
isOpen = true;
}else if (isOpen && mManLeft == 0){
mOnDraeListener.onclose(mDragLayout);
isOpen = false;
isStartOpen = false;
}else{
mOnDraeListener.onDrag(changedView, left); //接口回调
if (!isOpen && !isStartOpen){ //只有在关闭的情况下才是要开始打开了
mOnDraeListener.onStartOpen(mDragLayout);
isStartOpen = true;
}
}
}
invalidate();
}
// xvel //水平速度
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
if (xvel < 0) { //认为松手后还有左的,可以叫惯性速度么...就打开
open();
} else if (xvel == 0) { //不确定是关闭还是打开状态
if (isOpen) {
if (mMainView.getLeft() > -mRange / mRightChildeCount * (mRightChildeCount - 1)) {
close(); //打开状态,即向右滑超过三分之一关闭
} else {
open();
}
} else { //关闭状态跟上面一样向左滑
if (mMainView.getLeft() > -mRange / mRightChildeCount) {
close();
} else {
open();
}
}
} else if (xvel > 0) { //打开状态即向右滑
close();
}
}
}
public void open() {
mViewDragHelper.smoothSlideViewTo(mMainView, -mRange, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
public void close() {
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(this);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public boolean isOpen() {
return isOpen;
}
public interface onDraeListener {
void onDrag(View view, int left);
void onOpen(DragLayout dragLayout); //打开了
void onclose(DragLayout dragLayout); //关闭了
void onStartOpen(DragLayout dragLayout); //开始打开,不一定打开
}
private onDraeListener mOnDraeListener; //引用
public void setOnDraeListener(onDraeListener onDraeListener) { //由外部传个名字
mOnDraeListener = onDraeListener;
}
}
至于整合到我们上次写的QQ侧滑菜单,我是这样做的,先把那个ViewPager的onInterceptTouchEvent 和 onTouchEvent都返回false了,即它已经不能滑动了,然后把我们这个作为一个Fragment放到MyViewPage上去了,然后这还是会和侧滑产生滑动事件冲突的,向右滑的时候会滑菜单,因为SlideLayout是父容器嘛,事件被它拦截了,所以我在它的onInterceptTouchEvent做了一下修改
private float mStartX = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (celaIsOpen){ //如果侧拉打开了,不拦截向右滑动事件
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mStartX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
float movX = event.getRawX();
if (movX > mStartX){
return false;
}
break;
case MotionEvent.ACTION_UP:
mStartX = 0;
break;
}
}
return mViewDragHelper.shouldInterceptTouchEvent(event); //交给它去判断该不该拦截
}
有兴趣的可以去这下载整合的代码http://download.csdn.net/detail/z8z87878/9557522