鹅厂系列二 : 仿QQ侧拉删除
——-不知道说些啥..
嗯,前面我们仿着做了QQ的侧拉菜单,现在来做做侧拉删除,复习复习下我们ViewDragHelper的使用,先来看下效果吧
右边的是整合上次的侧滑菜单效果,没看过想了解的朋友可以去看看侧滑菜单的实现http://blog.csdn.net/z8z87878/article/details/51731221
额,控件缓慢拖动判断距离是否打开或关闭也做了,后面再录着看效果吧,好了,现在开始我们的自定义侧拉删除吧.其实这个和上次那个侧拉滑动还是挺像的是不是,其实是这样的,像这种自定义拖动容器,就是那几个类那几个方法,所以我们在熟悉这几个类和方法的情况下,其实自定义这种拖动容器是并不难的.开始自定义前,我们的第一个问题就是继承那个容器控件,如果自己继承ViewGroup的话,是挺不好的,它要我们写更多的代码,所以我们应该去继承那些常用的容器控件,其实也没几个是吧.我一开始根据这个控件的特性想的是继承LineLayout,单个条目是没问题的,但是把它整合到ListView的时候出问题了,不能显示,从来没碰过这种情况,我当时就懵了….后来试着继承FrameLayout.可以.所以我们这个控件继承FrameLayout,这为什么继承lineLayout然后ListView不显示,其中有多少不为人知的秘密……请你们探索着然后告诉我….
我们继承frameLayout要实现上面的效果,所以我们要重写onLayout布局方法
@Overrideprotected void onFinishInflate() { //视图解析完成后调用,这时候就能拿到我们的孩子对象了super.onFinishInflate();mRightView = getChildAt(0); //右边选项界面mMainView = getChildAt(1); //主面板}@Overrideprotected 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; //拖动范围}@Overrideprotected 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, 即不拖动*/@Overridepublic 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达到跟着动的效果,也可以在这设置回调之类的东西
@Overridepublic 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 //水平速度@Overridepublic 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);}@Overridepublic 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 ishorizontal. --><attr name="orientation" /><attr name="gravity" /><!-- When set to false, prevents the layout from aligning its children'sbaselines. This attribute is particularly useful when the childrenuse 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 baselinealigned, 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 computedby adding the layout_weight of all of the children. This can beused for instance to give a single child 50% of the total availablespace by giving it a layout_weight of 0.5 and setting the weightSumto 1.0. --><attr name="weightSum" format="float" /><!-- When set to true, all children with a weight will be considered havingthe minimum size of the largest child. If false, all children aremeasured 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
@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return mViewDragHelper.shouldInterceptTouchEvent(ev); //交给我们的mViewDragHelper去判断该不该拦截}@Overridepublic boolean onTouchEvent(MotionEvent event) {try {mViewDragHelper.processTouchEvent(event); //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下} catch (Exception e) {e.printStackTrace();}return true;}
好了,来看看不整合到ListView的效果
好吧,鼠标不好控制,刚开始的鼠标抬起带了点向左的速度所以打开了/没问题现在就可以整合到listView了,你会发现,打开后,滑右面板,向右滑动不了,为什么呢O(∩_∩)O~,因为和上一次侧拉一样,我们没有重写ViewDragHelper.CallBack中的这个方法
@Overridepublic 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;}@Overrideprotected void onFinishInflate() { //视图解析完成后调用,这时候就能拿到我们的孩子对象了super.onFinishInflate();mRightView = getChildAt(0); //右边选项界面mMainView = getChildAt(1); //主面板}@Overrideprotected 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; //拖动范围}@Overrideprotected 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);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return mViewDragHelper.shouldInterceptTouchEvent(ev); //交给我们的mViewDragHelper去判断该不该拦截}@Overridepublic boolean onTouchEvent(MotionEvent event) {try {mViewDragHelper.processTouchEvent(event); //交给我们的mViewDragHelper去操作触摸事件,事件被拦截有可能会出错,try一下} catch (Exception e) {e.printStackTrace();}return true;}class MyCallBack extends ViewDragHelper.Callback {@Overridepublic boolean tryCaptureView(View child, int pointerId) { //要求我们重写,返回true执行拖到事件,false不执行return true;}@Overridepublic int getViewHorizontalDragRange(View child) { //水平移动范围,我们最好重写这个方法!不然碰到listView拖不动return mRange;}/*** @param child 拖动那个孩子* @param left 拖动建议值,view根据返回的建议值拖动,相对于左位负,相对于右为正,是view相对物理窗口左边的位置* @param dx 变化值* @return 默认返回0, 即不拖动*/@Overridepublic 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;}@Overridepublic 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 //水平速度@Overridepublic 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);}@Overridepublic 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;@Overridepublic 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
鹅厂系列二 : 仿QQ侧拉删除相关推荐
- 鹅厂系列三 : 仿QQ消息拖动小球
未来会怎样,我不知道,我只是想为了比今天好 老规矩,看看效果 嗯,前面自定义了两个视图容器,今天这个是自定义View,开始自定义前,我们应该理清自己的思路,怎么来做这个东西.用我们的QQ,我们会发现, ...
- 鹅厂系列一 : 仿QQ侧滑菜单
--不会的东西你不尝试的去做,你永远都不会做 好了,跟随潮流,还是先看下效果,不然可能都没人想看下去了(不会看到效果后不想看了吧O(∩_∩)O~) 额,图片资源来自QQ_374.APK,里面四五千个图 ...
- 仿QQ拖动删除未读消息个数气泡之二
在仿QQ拖动删除未读消息个数气泡这篇文章中,模仿了QQ的删除未读消息气泡,不过也遗留了一个问题,当时为了让气泡能够在全屏范围内拖动,不能将其放在布局文件xml中,而是采用了在主布局加载完成后用addV ...
- 仿QQ侧滑删除,Listview上下滑动,Listview的iteam的点击事件等bug的解决
网上ListView横向滑动删除Item这样的介绍也很多,但实用性不强,没有解决横向滑动和item的点击事件的冲突,废话少说,有图有真相,下面直接上代码 1:侧滑 2:侧滑的点击事件 3:i ...
- 高仿 QQ 侧滑删除 Item 的效果
<span style="font-size:24px;"><span style="white-space:pre"> </sp ...
- 仿QQ侧滑删除ListView——2015第一博
一直感觉QQ最近联系人那个侧滑删除功能挺高大上的,经过几经波折,终于在新的一年里实现了该功能.实现这个功能真是费了老劲了,好几次有了想法,兴奋的去写代码实现,结果让代码打了自己一个耳光,最终还是用ma ...
- Android动画效果(二) 仿QQ点赞动画
近日有看到QQ点赞的动画效果,于是模仿写了一个 要实现图中效果,需要用到属性动画,属性动画利用ValueAnimator来跟踪记录对象属性已经变化了多长时间及当前这个时间点的值. 如果不设置的话,动画 ...
- 自定义View 篇四《低仿QQ测拉删除》
都知道QQ有一个比较牛逼的效果就是测拉删除效果,目前这个功能,网上自定义控件也有很多实现方式了,本篇也自己实现一个测拉删除效果的自定义控件.虽然功能一样,实现方式不同罢了,也希望提供一些思路,对自己和 ...
- Android学习之仿QQ側滑功能的实现
如今项目越来越多的应用了滑动删除的功能,Android本来遵循的是长按删除,IOS定制的是滑动删除,不可否认滑动删除确实在客户体验上要好一点,所以看了非常多关于仿QQ滑动删除的样例,还是感觉代码家的A ...
最新文章
- tensorflow 代码调试工具tfdbg的用法
- 交叉熵损失函数在多分类深度学习中的应用 转
- 腾讯多任务模型MFH
- Linux设置Oracle开机自启动
- “豆瓣酱”之用户,场景,功能
- 点击windows live writer无反应 / 使用windows live writer的前提
- 机器学习-朴素贝叶斯分类
- nginx upstream 模块详解
- HLSL编译工具—FXC
- OpenGL ES 送显 YUV NV12
- 开源免费的pdf文档编辑器LibreOffice
- C# object 转 Intptr, Intptr 转 object
- 中文分词 及发展现状(总结的不错)
- 程序员技术入股的那些坑!保护好你的核心技术,想走?没那么容易!
- C语言实现乘方运算---m的n次方(附完整源码)
- 细数红帽linux系统下的各个文件夹作用
- Unity改变应用游戏的分辨率 resoution
- Linux内核中喂狗,m3352linux内核中看门狗喂狗和应用层喂狗方法
- 如何实现通过回车键提交表单
- 使用Sportsvu数据创建NBA动作视频