QQListview左滑删除,经典案例,高仿QQ左滑,动画效果,自定义!!

     虽然有很多开放写得已经很完美的listview开源框架, 用起来也很方便, 只需修改下布局,   大致套路都一样, 几乎没什么bug。 今天我们来看看自己定义的QQListview;


在这篇文章你能学到什么?

    

   1.事件冲突---listview 上下滑动和里面Item左右滑动 冲突(解决方法: 用外部拦截法, 判断滑动角度,是否大一定角度,然后进行拦截)

  2.VelocityTracker 使用, 计算;

3. handler结合targetview处理view动画(这里有多种方式比如Scroller , )



开始吧

    先看下Listview 的Item布局吧 


<span style="color:#3333ff;"><?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

   

    <TextView
        android:layout_gravity="right"
        android:id="@+id/dele"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        android:gravity="center"
        android:padding="20dp"
        android:text="删除"
        android:enabled="false"
        android:textColor="#fff" />
    
     <TextView
        android:id="@+id/alp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:background="#ffffff"
        android:text="A" />

</FrameLayout></span>
     这里比较简单就一个FrameLayout,嵌套两个TextView,需要注意的是id为alp的TextView为前景TextView,id为deleTextView正常情况下是不可见, 我们要做的事情, 当user手势向左滑动的时候, 把deleTextView显示出来。


再来看看QQListView核心代码

 

public class QQListView extends ListView {

	/**
	 * 屏幕宽度
	 */
	private int width;
	/**
	 * 屏幕高度
	 */
	private int height;
	/**
	 * 系统默认最小的滑动距离,
	 */
	private int slop;
	/**
	 * 速度追踪者
	 */
	private VelocityTracker mVelocityTracker;
	/**
	 * 当用户按下Item的时候记录的 前景TextView
	 */
	private View target;
	/**
	 * 上一次X坐标
	 */
	private int lastX;
	/**
	 * 上一次Y坐标
	 */
	private int lastY;
	/**
	 * 滑动的模式
	 */
	private int scrollModel;
	/**
	 * 判断是否需要改变滑动模式
	 */
	boolean isNeed=true;
	/**
	 * 背景TextView宽度
	 */
	private int backgroudViewWidht;
	/**
	 * 表示背景TextView是否已经显示
	 */
	boolean isShow=false;
	/**
	 * 前景TextView
	 */
	private View showView;
	/**
	 * 背景TextView
	 */
	private View backgroudView;
	
	public QQListView(Context context) {
		this(context,null);
	}

	public QQListView(Context context, AttributeSet attrs) {
		this(context, attrs,0);
		// 获取屏幕的宽高
		
		
	}
	public QQListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		
		DisplayMetrics displayMetrics = context.getResources()
				.getDisplayMetrics();
		height = displayMetrics.heightPixels;
		width = displayMetrics.widthPixels;

		slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}
这里贴了一些变量,和构造方法,  构造方法第一个构造方法调用第二个构造方法,第二个构造方法调用三个参数构造方法,一般来说, 第一个构造参数的构造方法在java文件new的时候调用,第二个构造方法在xml文件不指定style时候调用,指定调用第三个,我们这里初始化了,屏幕宽高;

onTouchEvent 的事件

 先看下down事件

     

<span style="font-size:24px;">case MotionEvent.ACTION_DOWN:
			if(isShow==true)  // 表示背景View(删除TextView)可见的时候,在触发down就把背景View 隐秘掉
			{
				Log.i("Info ", "is show");
				closeViewByAnimation(showView); 
				
			}
			lastX=(int) ev.getX();  //获取当前X坐标(这里LastX是相对父控件)
			lastY=(int) ev.getY();
			getTargetView(ev,lastX,lastY);  //获取点击按下那个Item里面的前景TextView
			break;</span>
    

这里说明一下,在onTouchEvent方法里面的Down 事件 必须返回super.onTouchEvent(ev) ,不然后续ItemOnItemClickListener等事件不会响应,具体要深入listview的源码,有兴趣的可以去了解下, listview本身为我们做了很多事件处理了。 我们这里主要获取下点击的Item(

<span style="font-size:24px;">getTargetView(ev,lastX,lastY);</span>
)获取对应的TextView,  也就是(前景TextView) 具体怎么获取 看下面

	private View getTargetView(MotionEvent ev,int x,int y) {
		
		mVelocityTracker=VelocityTracker.obtain();
		mVelocityTracker.addMovement(ev);
		
		ViewGroup viewGroup=(ViewGroup) this.getChildAt(pointToPosition(x, y)-this.getFirstVisiblePosition()); //获取外层的FrameLayout
		if(viewGroup!=null)  //这里需要盘空,有各种意外情况  (比如点了分割线 没有获取到)
		{
			
			View view=viewGroup.getChildAt(1);  //前景TextView
			target=view; //赋值给target 我们要对target进行一系列动画处理
			backgroudView = viewGroup.getChildAt(0);   //获取backgroudview,然后其宽度
			backgroudView.setEnabled(true);
			backgroudViewWidht = backgroudView.getWidth();
		}
		
		return null;
		
		
	}


前面两行把速度追踪者初始化了  VelocityTracker    用来计算单位时间内用户滑动的像素。 看看Move事件吧


case MotionEvent.ACTION_MOVE:
			
			 int moveX=(int) ev.getX();   //获取触摸当前move事件下得X Y坐标
			 int moveY=(int) ev.getY();
			 if(mVelocityTracker==null)
			 {
				mVelocityTracker=VelocityTracker.obtain();
			 }
			 mVelocityTracker.clear();  //清空一些数据
			 mVelocityTracker.addMovement(ev); 
			 mVelocityTracker.computeCurrentVelocity(500);  //计算500ms 用户滑动速度,注意这里是有方向区别的
			 
			 if(scrollModel!=ITEMSCROLLMODE&&isNeed==true) //判断是不是item滑动模式
			 {
				 if(Math.abs(mVelocityTracker.getXVelocity())<=50){  
					 Log.i("Info", "速度太慢无法触发 X:"+mVelocityTracker.getXVelocity());
					 lastX=(int) ev.getX();
					 lastY=(int) ev.getY();  //把当前X Y赋值给lastX lastY
					 return super.onTouchEvent(ev);   // 这里触发listview上下滑动
				 }
				 
				 Log.i("Info", "tag4");
				 if(Math.abs(moveX-lastX)>Math.abs(moveY-lastY)){   //判断是左滑还是上下滑动, 这里默认X>Y则为Item滑动,也就是左滑删除
					 scrollModel=ITEMSCROLLMODE;
					 isNeed=false;
				 }
				 else{
					 isNeed=false;
				 }
			 
			 }
			 
			 if(moveX-lastX>0){//大于零 说明已经往右滑
				 
				moveX=lastX;
			 }
			 
			 
			 if(scrollModel==LISTVIEWSCROLLMODEL||target==null)
			 {
//				 Log.i("Info", "scrollModel: "+ scrollModel);
				 return super.onTouchEvent(ev);  //不处理, 交个listview 做上下滑动处理
			 }else{
				 
				 int deltaX=moveX-lastX;
				 ViewHelper.setTranslationX(target, deltaX); //移动target(前景TextView)显示背景TextView(删除TextView)
				 
				 return true;
			 }

       Move事件处理可以说整个QQListView核心了,注释已经写的很详细了。这里做了很多逻辑代码判断, 继续看Up事件

   

case MotionEvent.ACTION_UP:
			try {
				
				
				
				mVelocityTracker.clear();  //清除下数据
				mVelocityTracker.recycle(); //回收下
				
				if(isShow)
				{
					Log.i("Info", "消费事件");
					isShow=false;
					stateRestore();  //恢复先原来状态
					return true;    // 如果当前是显示状态, 把事件消费掉  结束整个事件分发
				}
				
				if(scrollModel==ITEMSCROLLMODE) 
				{
//					Log.i("Info", "up return "+"true");
					restoreOrSmoonth(ev,target);  //关闭前景TextView
					stateRestore();
					return true;
				} 
					stateRestore();
				 
				
			} catch (Exception e) {   //这里捕捉下异常
				
				Log.e("Info", "up  事件 异常");
			}
			
			break;
		}
		
		return super.onTouchEvent(ev);

      在UP事件把一些东西释放掉, 然后就是把显示的View关闭。 

     然后我们来看看动画吧  ,   这里来分析下, 在下图可以看出, 我们滑动大于一般的时候松开手, 会自动移动到把整个删除按钮都显示出来, 这里需要是实现 是用ViewHelper+Handler实现, 先简单讲下思路 ,删除按钮显示一半, 需要把另一把的长度计算出来, 这一段长度就是我们动画自动移动的长度, 把这一段长度, 分成若干帧

用算出来的长度/帧数=每帧移动的长度, 我这里默认30帧,默认 时长 1000ms 然后用handler  postMessageDelay()这里可以控制时间   总之核心思想 类似 微积分,把一个大的东西小分化了, 无数小东西,  这里也一样, 一秒播放了三十张图片  , 看代码吧


     

private void closeViewByAnimation(View showView_) {
		
		Message message=new Message();
		message.what=0x22;
		message.obj=showView_;
		from=-backgroudViewWidht;  
		distance=backgroudViewWidht;
		handler.sendMessage(message);
		
		
	}



	boolean isRuning=false;
	private Scroller mScroller;
	private void restoreOrSmoonth(MotionEvent ev ,View v) {
		
		Log.i("Info", "v:"+v+"   isRuning:"+isRuning);
		if(v==null||isRuning==true){
			return;
		}
		
		isRuning=true;
		int endX=(int) ev.getX();
		int deltaX=endX-lastX;
		Log.i("Info", "触发动画");
		if(deltaX<0){//左滑
			
			if(Math.abs(deltaX)>backgroudViewWidht/2){
				int oppsiteDeltaX=backgroudViewWidht-Math.abs(deltaX);
				
				Log.i("Info", "x oppsi:"+oppsiteDeltaX+"  deltaX:"+deltaX);
				
				from=deltaX;
				distance=-oppsiteDeltaX;
				Message message=handler.obtainMessage();
				Log.i("Info", "target:"+v);
				message.what=0x22;
				message.obj=v;
				handler.sendMessage(message);
				
				showView=v;
				isShow=true;//显示View
				Log.i("Info", "isShow:"+isShow);
				 
				isRuning=false;
			}
			else{//如果滑动大于bacgroud的一半
				 
				int oppsiteDeltaX=backgroudViewWidht-Math.abs(deltaX);
 
				Log.i("Info", "s oppsi:"+oppsiteDeltaX+"  deltaX:"+deltaX);
				from=deltaX;
				distance=-deltaX;
				Message message=handler.obtainMessage();
				message.what=0x22;
				message.obj=v;
				handler.sendMessage(message);
				Log.i("Info", "isShow_:"+isShow);
				isShow=false;
				isRuning=false;
				
			}
		}
		
		isRuning=false;
	}
	
	float from;  //从哪里开始
	float distance;  //滑动到哪里
	float frame=30;   //帧数
	float duration=100;  //时长
	float count=0;   //用于计数
	Handler handler=new Handler(){
		public void handleMessage(android.os.Message msg) {
			if(msg.what==0x22)
			{
				if(count<=frame)   //小于帧数继续走
				{
					++count;
					View view=(View) msg.obj;
					Message message=handler.obtainMessage();
					message.what=0x22;
					
					message.obj=view;
					ViewHelper.setTranslationX(view, from+distance/frame*count);  //view移动的距离
					handler.sendMessageDelayed(message,(int)duration/100); //继续发送消息直到移动结束
					
				}else
				{
					count=0;
				}
				
			}
			
			
			
		};
	};
           这里的handler 主要用来控制时间和帧数    。。 好了 分析就到这里了 .





 

     


   

   

    


冲 突处理;

发布了7 篇原创文章 · 获赞 3 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览