QQListview左滑刪除,經典案例,高仿QQ左滑,動畫效果,自定義!!
雖然有很多開放寫得已經很完美的listview開源框架, 用起來也很方便, 只需修改下佈局, 大致套路都一樣, 幾乎沒什麼bug。 今天我們來看看自己定義的QQListview;
在這篇文章你能學到什麼?
1.事件衝突---listview 上下滑動和裡面Item左右滑動 衝突(解決方法: 用外部攔截法, 判斷滑動角度,是否大一定角度,然後進行攔截)
2.VelocityTracker 使用, 計算;
3. handler結合targetview處理view動畫(這裡有多種方式比如Scroller , )
開始吧
先看下Listview 的Item佈局吧
這裡比較簡單就一個FrameLayout,巢狀兩個TextView,需要注意的是id為alp的TextView為前景TextView,id為deleTextView正常情況下是不可見, 我們要做的事情, 當user手勢向左滑動的時候, 把deleTextView顯示出來。<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>
再來看看QQListView核心程式碼
這裡貼了一些變數,和構造方法, 構造方法第一個構造方法呼叫第二個構造方法,第二個構造方法呼叫三個引數構造方法,一般來說, 第一個構造引數的構造方法在java檔案new的時候呼叫,第二個構造方法在xml檔案不指定style時候呼叫,指定呼叫第三個,我們這裡初始化了,螢幕寬高;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(); }
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 主要用來控制時間和幀數 。。 好了 分析就到這裡了 .
衝 突處理;