1. 程式人生 > >QQListview左滑刪除,經典案例,高仿QQ左滑,動畫效果,自定義!!

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 主要用來控制時間和幀數    。。 好了 分析就到這裡了 .




 

衝 突處理;